Commit 5870d430 authored by Rais Aryaguna's avatar Rais Aryaguna

fix bulanan

parent a9b4d62a
import { Close } from '@mui/icons-material';
import { LoadingButton } from '@mui/lab';
import {
Alert,
Box,
CircularProgress,
Dialog,
DialogContent,
DialogTitle,
Grid,
IconButton,
Stack,
Typography,
} from '@mui/material';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { useSelector } from 'react-redux';
import { useAuthContext } from 'src/auth/hooks';
import { Field } from 'src/components/hook-form';
import { useAppSelector } from 'src/store';
interface DialogPenandatanganProps {
isOpen: boolean;
onClose: () => void;
title?: string;
// onSubmit: (data: any) => void;
// isLoadingButtonSubmit: boolean;
// agreementText?: string;
// isPembatalan: boolean;
// isPending: boolean;
// feature: string;
// isWarning: boolean;
// isCountDown: boolean;
}
export default function DialogPenandatangan({
isOpen,
onClose,
title = 'Penandatangan',
}: DialogPenandatanganProps) {
const penandatanganOptions = useAppSelector((state) => state.user.data.signer_npwp);
const form = useForm({
mode: 'all',
});
const [isCheckedAgreement, setIsCheckedAgreement] = useState(false);
const handleClose = () => {
form.reset();
onClose();
};
// const declareOptions = [
// { label: feature === 'spt faktur' ? 'PKP' : 'Wajib Pajak', value: 0 },
// { label: 'Wakil/Kuasa', value: 1 },
// ];
const handleSubmitLocal = (data: any) => {
// if (isCountDown)
// setCountdown(30); // start countdown saat submit
// else setCountdown(null);
// onSubmit(data); // tetap panggil props onSubmit
};
return (
<Dialog
fullWidth
maxWidth="md"
open={isOpen}
onClose={handleClose}
aria-labelledby="dialog-rekap"
>
<DialogTitle id="dialog-rekap">
<Typography textTransform="capitalize" fontWeight="bold" variant="inherit" color="initial">
{title}
</Typography>
</DialogTitle>
<IconButton
aria-label="close"
onClick={handleClose}
sx={(theme) => ({
position: 'absolute',
right: 8,
top: 8,
color: theme.palette.grey[500],
})}
>
<Close />
</IconButton>
<DialogContent>
{/* <form onSubmit={form.handleSubmit(handleSubmitLocal)}> */}
<Field.Autocomplete
name="nikNpwpTtd"
label="NPWP/NIK Penandatangan"
options={[{ value: penandatanganOptions, label: `NAMA${penandatanganOptions}` }]}
sx={{ background: 'white' }}
/>
{/*
<Agreement
isCheckedAgreement={isCheckedAgreement}
setIsCheckedAgreement={setIsCheckedAgreement}
text={agreementText}
/>
<LoadingButton
loading={isLoadingButtonSubmit}
disabled={!isCheckedAgreement}
variant="contained"
type="submit"
>
Save
</LoadingButton> */}
{/* </form> */}
</DialogContent>
</Dialog>
);
}
......@@ -67,6 +67,8 @@ function JumlahPerhitunganForm() {
metode: fgPerhitungan !== '0' ? METODE_POTONG.GROSS_UP : METODE_POTONG.GROSS, // Simplify conditional assignment
};
console.log('🚀 ~ handleHitung ~ data:', data);
mutate(data as any);
};
return (
......
const appRootKey = 'unifikasi';
const appRootKey = 'bupot';
const queryKey = {
getKodeObjekPajak: (params: any) => [appRootKey, 'kode-objek-pajak', params],
......
import { useQuery } from '@tanstack/react-query';
import { isEmpty } from 'lodash';
import { FG_PDF_STATUS, FG_SIGN_STATUS } from '../constant';
import { useQuery } from '@tanstack/react-query';
import queryKey from '../constant/queryKey';
import type {
TBaseResponseAPI,
......@@ -93,7 +93,7 @@ const useGetBulanan = ({ params, ...props }: any) => {
const query = useQuery<TBaseResponseAPI<TGetListDataTableDnResult>>({
queryKey: queryKey.bulanan.all(params),
queryFn: async () => {
const response = await bulananApi.getBulanan({ params: normalisPropsParmasGetDn(params) });
const response = await bulananApi.getList({ params: normalisPropsParmasGetDn(params) });
return {
...response,
......
import { useMutation } from '@tanstack/react-query';
import { useMutation, type UseMutationOptions } from '@tanstack/react-query';
import dayjs from 'dayjs';
import queryKey from '../constant/queryKey';
import type { TPostBulananRequest } from '../types/types';
import bulananApi from '../utils/api';
import type { TPostDnRequest } from '../types/types';
const extractKapFromKodeObjekPajak = (kodeObjekPajak: string) =>
`4111${kodeObjekPajak.split('-')[0]}`;
const transformParams = ({ isPengganti = false, ...Data }: any): TPostDnRequest => {
const transformParams = ({ isPengganti = false, ...Data }: any): TPostBulananRequest => {
const {
id,
idBupot,
......@@ -27,7 +28,6 @@ const transformParams = ({ isPengganti = false, ...Data }: any): TPostDnRequest
tglPemotongan,
idTku,
fgGrossUp,
komponen,
alamatDipotong,
foreignEmployee,
passportNo,
......@@ -75,14 +75,15 @@ const transformParams = ({ isPengganti = false, ...Data }: any): TPostDnRequest
},
tglPemotongan: tglPemotongan ? dayjs(tglPemotongan).format('DDMMYYYY') : '',
fgGrossUp: fgGrossUp ?? 1,
komponen: komponen ?? [],
};
};
const useSaveBulanan = (props?: any) =>
const useSaveBulanan = (
props?: Omit<UseMutationOptions<any, Error, any, unknown>, 'mutationKey' | 'mutationFn'>
) =>
useMutation({
mutationKey: ['Save-Dn'],
mutationFn: (params: any) => bulananApi.saveBulanan(transformParams(params)),
mutationKey: queryKey.bulanan.draft,
mutationFn: (params: any) => bulananApi.save(transformParams(params)),
...props,
});
......
import { useMutation, type UseMutationOptions } from '@tanstack/react-query';
import bulananApi from '../utils/api';
import type { TPortBulananUploadRequest } from '../types/types';
import queryKey from '../constant/queryKey';
const useUploadBulanan = (
props?: Omit<
UseMutationOptions<any, Error, TPortBulananUploadRequest, unknown>,
'mutationKey' | 'mutationFn'
>
) =>
useMutation<any, Error, TPortBulananUploadRequest, unknown>({
mutationKey: queryKey.bulanan.upload,
mutationFn: (params: TPortBulananUploadRequest) => bulananApi.upload(params),
...props,
});
export default useUploadBulanan;
......@@ -181,7 +181,7 @@ export type ActionItem = {
disabled?: boolean;
};
export type TPostDnRequest = {
export type TPostBulananRequest = {
id?: number | null;
npwpPemotong: string;
idTku: string;
......@@ -215,8 +215,8 @@ export type TPostDnRequest = {
idBupot?: string;
revNo: number;
fgGrossUp: number;
komponen: Array<{
kode: string;
value: string;
}>;
};
export type TPortBulananUploadRequest = {
id: string;
};
import { fetcher } from 'src/lib/axios';
import type {
BupotRecord,
TBaseResponseAPI,
TGetListDataKOPDnResult,
TGetListDataTableDnResult,
TPostDnRequest,
TPortBulananUploadRequest,
TPostBulananRequest,
} from '../types/types';
import bulananClient from './bulananClient';
const bulananApi = () => {};
// API untuk get list table
bulananApi.getBulanan = async (config: any) => {
const {
data: { message, metaPage, data },
status: statusCode,
} = await bulananClient.get<TBaseResponseAPI<TGetListDataTableDnResult>>('IF_TXR_028/a0', {
...config,
});
if (statusCode !== 200) {
throw new Error(message);
bulananApi.getList = async (config: any) => {
const response = await fetcher<TBaseResponseAPI<TGetListDataTableDnResult>>([
'IF_TXR_028/a0',
{
method: 'GET',
...config,
},
]);
if (!response || response.status !== 'success') {
throw new Error(response?.message || 'Failed to fetch bulanan data');
}
const { message, metaPage, data } = response;
return { total: metaPage ? Number(metaPage.totalRow) : 0, data };
};
// ✅ adjust biar bisa terima params
bulananApi.getKodeObjekPajakBulanan = async (params?: Record<string, any>) => {
const response = await bulananClient.get<TBaseResponseAPI<TGetListDataKOPDnResult>>(
bulananApi.getKodeObjekPajak = async (params?: Record<string, any>) => {
const response = await fetcher<TBaseResponseAPI<TGetListDataKOPDnResult>>([
'/sandbox/mst_kop_bpu',
{ params } // ⬅️ inject ke query string
);
{
method: 'GET',
params, // ⬅️ inject ke query string
},
]);
if (!response || response.status !== 'success') {
throw new Error(response?.message || 'Failed to fetch kode objek pajak');
}
const body = response.data;
return response;
};
bulananApi.save = async (config: TPostBulananRequest) => {
const response = await fetcher<TBaseResponseAPI<BupotRecord>>([
'/IF_TXR_028/a0',
{
method: 'POST',
data: config,
},
]);
if (response.status !== 200 || body.status !== 'success') {
throw new Error(body.message);
if (!response || response.status !== 'success') {
throw new Error(response?.message || 'Failed to save bulanan data');
}
return body;
return response.data;
};
bulananApi.saveBulanan = async (config: TPostDnRequest) => {
const {
data: { message, data },
status: statusCode,
} = await bulananClient.post<TBaseResponseAPI<BupotRecord>>('/IF_TXR_028/a0', {
...config,
});
if (statusCode !== 200) {
throw new Error(message);
bulananApi.upload = async (config: TPortBulananUploadRequest) => {
const response = await fetcher<TBaseResponseAPI<BupotRecord>>([
'/IF_TXR_028/a0/upload',
{
method: 'POST',
data: config,
},
]);
if (!response || response.status !== 'success') {
throw new Error(response?.message || 'Failed to upload bulanan data');
}
return data;
return response.data;
};
export default bulananApi;
import axios from 'axios';
import { CONFIG } from 'src/global-config';
const BASE_URL = CONFIG.nodesandbox.api;
const unifikasiClient = axios.create({
baseURL: BASE_URL,
validateStatus(status) {
return (status >= 200 && status < 300) || status === 500;
},
});
// Interceptor untuk selalu update token dari localStorage
unifikasiClient.interceptors.request.use((config) => {
const jwtAccessToken = localStorage.getItem('jwt_access_token');
const xToken = localStorage.getItem('x-token');
if (jwtAccessToken) {
config.headers.Authorization = `Bearer ${jwtAccessToken}`;
}
if (xToken) {
config.headers['x-token'] = xToken;
}
return config;
});
export default unifikasiClient;
......@@ -10,7 +10,6 @@ import { useMemo, useState } from 'react';
import TableHeaderLabel from 'src/shared/components/TableHeaderLabel';
import useGetBulanan from '../hooks/useGetBulanan';
import { isEmpty } from 'lodash';
import { Typography } from '@mui/material';
// import CustomToolbarDn from '../components/customToolbarDn';
// import CustomToolbar, { CustomFilterButton } from '../components/customToolbarDn2';
......@@ -114,8 +113,6 @@ export function BulananListView() {
const totalRows = data?.total || 0;
const rows = useMemo(() => data?.data || [], [data?.data]);
console.log({ data: data.data[0] });
// const handleChange = (event: React.SyntheticEvent, newValue: number) => {
// setTabs1(newValue);
// };
......@@ -214,9 +211,7 @@ export function BulananListView() {
headerName: 'NPWP',
headerAlign: 'center',
align: 'left',
minWidth: 200,
renderCell: (params) => <Typography className="text-[14px] ">{params.value}</Typography>,
},
{
field: 'namaDipotong',
......@@ -350,6 +345,7 @@ export function BulananListView() {
initialState={{
pagination: { paginationModel: { pageSize: 10, page: 0 } },
}}
disableVirtualization
pagination
pageSizeOptions={[5, 10, 15, 25, 50, 100, 250, 500, 750, 100]}
paginationMode="server"
......
......@@ -28,6 +28,7 @@ import {
} from '../constant';
import { checkCurrentPage } from '../utils/utils';
import useSaveBulanan from '../hooks/useSaveBulanan';
import DialogPenandatangan from '../../DialogPenandatangan';
const bulananSchema = z
.object({
......@@ -75,7 +76,7 @@ const bulananSchema = z
.refine((data) => data.value !== '', {
message: 'PTKP wajib dipilih',
}),
email: z.string().email('Email tidak valid').nullable().optional(),
email: z.string().optional(),
jumlahPenghasilan: z.array(z.object({})).optional(),
jumlahPengurangan: z.array(z.object({})).optional(),
idTku: z
......@@ -174,6 +175,7 @@ export const BulananRekamView = () => {
const [isOpenPanduan, setIsOpenPanduan] = useState<boolean>(false);
const [isCheckedAgreement, setIsCheckedAgreement] = useState<boolean>(false);
const [isOpenDialogPenandatangan, setIsOpenDialogPenandatangan] = useState(false);
const actionRekam = checkCurrentPage(pathname);
const dataListKOP = useMemo(
......@@ -248,9 +250,9 @@ export const BulananRekamView = () => {
statusPtkp: data.ptkp?.value || '',
kdObjPjk: data.kdObjPjk.value,
kdJnsPjk: 'PPh Pasal 21', // Sesuaikan dengan konstanta Anda
jmlBruto: parseInt(data.phBruto || '0', 10),
tarif: parseInt(data.tarif || '0', 10),
pphDipotong: parseInt(data.pph21 || '0', 10),
jmlBruto: data.phBruto,
tarif: data.tarif,
pphDipotong: data.pph21,
fgFasilitas: data.fgFasilitas.value,
noDokLainnya: data.skb || '',
fgGrossUp: parseInt(data.fgPerhitungan || '1', 10),
......@@ -261,6 +263,10 @@ export const BulananRekamView = () => {
await saveBulanan(transformedData);
};
const handleClickUpload = async () => {
setIsOpenDialogPenandatangan(true);
};
const SubmitRekam = async (data: BpuFormData) => {
const respon = await handleDraft(data);
console.log({ respon });
......@@ -276,83 +282,91 @@ export const BulananRekamView = () => {
];
return (
<DashboardContent>
<CustomBreadcrumbs
heading="Add Bupot PPh Pasal 21 Bulanan"
links={[
{ name: 'Dashboard', href: paths.dashboard.root },
{
name: 'e-Bupot PPh Pasal 21 Bulanan',
href: paths.pph21.bulanan,
},
{ name: 'Add Bupot PPh Pasal 21 Bulanan' },
]}
/>
<>
<DashboardContent>
<CustomBreadcrumbs
heading="Add Bupot PPh Pasal 21 Bulanan"
links={[
{ name: 'Dashboard', href: paths.dashboard.root },
{
name: 'e-Bupot PPh Pasal 21 Bulanan',
href: paths.pph21.bulanan,
},
{ name: 'Add Bupot PPh Pasal 21 Bulanan' },
]}
/>
<HeadingRekam label="Rekam Bupot PPh Pasal 21 Bulanan" />
<Grid container columnSpacing={2}>
<Grid size={{ xs: isOpenPanduan ? 8 : 11 }}>
<form onSubmit={methods.handleSubmit(SubmitRekam)}>
<FormProvider {...methods}>
<Suspense fallback={<FormSkeleton />}>
<Identitas />
<HeadingRekam label="Rekam Bupot PPh Pasal 21 Bulanan" />
<Grid container columnSpacing={2}>
<Grid size={{ xs: isOpenPanduan ? 8 : 11 }}>
<form onSubmit={methods.handleSubmit(SubmitRekam)}>
<FormProvider {...methods}>
<Suspense fallback={<FormSkeleton />}>
<PerhitunganPPhPasal21 kodeObjectPajak={dataListKOP} />
</Suspense>
<Identitas />
<JumlahPerhitunganForm />
<Suspense fallback={<FormSkeleton />}>
<PerhitunganPPhPasal21 kodeObjectPajak={dataListKOP} />
</Suspense>
<Grid size={{ md: 12 }}>
<Field.Autocomplete
name="idTku"
label="NITKU Pemotong"
options={MockNitku.map((a) => ({ value: a.nama, label: a.nama }))}
/>
</Grid>
<JumlahPerhitunganForm />
<Divider />
<Grid size={{ md: 12 }}>
<Field.Autocomplete
name="idTku"
label="NITKU Pemotong"
options={MockNitku.map((a) => ({ value: a.nama, label: a.nama }))}
/>
</Grid>
<Grid size={12}>
<Agreement
isCheckedAgreement={isCheckedAgreement}
setIsCheckedAgreement={setIsCheckedAgreement}
text="Dengan ini saya menyatakan bahwa Bukti Pemotongan/Pemungutan Unifikasi telah
<Divider />
<Grid size={12}>
<Agreement
isCheckedAgreement={isCheckedAgreement}
setIsCheckedAgreement={setIsCheckedAgreement}
text="Dengan ini saya menyatakan bahwa Bukti Pemotongan/Pemungutan Unifikasi telah
saya isi dengan benar secara elektronik sesuai
dengan"
/>
</Grid>
/>
</Grid>
<Stack direction="row" gap={2} justifyContent="end" marginTop={2}>
<LoadingButton
type="submit"
loading={isSaving}
disabled={!isCheckedAgreement}
variant="outlined"
sx={{ color: '#143B88' }}
>
Save as Draft
</LoadingButton>
<LoadingButton
type="button"
disabled={!isCheckedAgreement}
onClick={methods.handleSubmit(SubmitRekam)}
loading={isSaving}
variant="contained"
sx={{ background: '#143B88' }}
>
Save and Upload
</LoadingButton>
</Stack>
</Suspense>
</FormProvider>
</form>
</Grid>
<Grid size={{ xs: isOpenPanduan ? 4 : 1 }}>
<PanduanDnRekam handleOpen={handleOpenPanduan} isOpen={isOpenPanduan} />
<Stack direction="row" gap={2} justifyContent="end" marginTop={2}>
<LoadingButton
type="submit"
loading={isSaving}
disabled={!isCheckedAgreement}
variant="outlined"
sx={{ color: '#143B88' }}
>
Save as Draft
</LoadingButton>
<LoadingButton
type="button"
disabled={!isCheckedAgreement}
onClick={methods.handleSubmit(handleClickUpload)}
loading={isSaving}
variant="contained"
sx={{ background: '#143B88' }}
>
Save and Upload
</LoadingButton>
</Stack>
</Suspense>
</FormProvider>
</form>
</Grid>
<Grid size={{ xs: isOpenPanduan ? 4 : 1 }}>
<PanduanDnRekam handleOpen={handleOpenPanduan} isOpen={isOpenPanduan} />
</Grid>
</Grid>
</Grid>
</DashboardContent>
</DashboardContent>
<DialogPenandatangan
isOpen={isOpenDialogPenandatangan}
onClose={() => {
setIsOpenDialogPenandatangan(false);
}}
/>
</>
);
};
......@@ -4,7 +4,13 @@ import {
KODE_OBJEK_PAJAK,
PERHITUNGAN_BUPOT21,
} from './bupot-bulanan/constant';
import type { paramsHitung } from './type';
export type paramsHitung = {
kodeObjekPajak: string;
fgPerhitungan: string;
jenisPerhitungan: string;
phBruto: number;
};
export const checkPerhitunganBupot21 = ({
kodeObjekPajak,
......
import {
useMutation,
type UseMutationResult,
type UseMutationOptions,
type UseMutationResult,
} from '@tanstack/react-query';
import type { AxiosError } from 'axios';
import { fetcherHitung, endpoints } from 'src/lib/axios-ctas-box';
import type { transformParamsBupotBulananProps } from './type';
import { checkPerhitunganBupot21 } from './helper';
import { endpoints, fetcherHitung } from 'src/lib/axios-ctas-box';
import { PERHITUNGAN_BUPOT21 } from './bupot-bulanan/constant';
import { checkPerhitunganBupot21, type paramsHitung } from './helper';
// ========================================
// Types
// ========================================
export type transformParamsBupotBulananProps = {
status: string;
metode: 'gross' | 'gross-up';
tglBupot: string;
penghasilanKotor: string;
dppPersen: string;
} & paramsHitung;
interface ApiResponse<T> {
code: number;
status: string;
......@@ -153,30 +159,6 @@ const handleHitungBulanan = async (
// React Query Hook
// ========================================
/**
* Hook untuk hitung PPh 21 Bulanan
*
* @example
* ```tsx
* const { mutate, isPending, error } = useHitungBulanan({
* onSuccess: (data) => {
* console.log('PPh21:', data.pph21);
* },
* onError: (error) => {
* toast.error(error.message);
* }
* });
*
* mutate({
* status: 'TK/0',
* tglBupot: '2025-01-15',
* penghasilanKotor: 5000000,
* dppPersen: 5,
* metode: 'gross'
* });
* ```
*/
export function useHitungBulanan(
props?: MutationProps
): UseMutationResult<ApiResponseBulanan, AxiosError, transformParamsBupotBulananProps> {
......@@ -191,9 +173,6 @@ export function useHitungBulanan(
// Error Handler Utility (Optional)
// ========================================
/**
* Helper untuk handle error dari mutation
*/
export const getHitungBulananErrorMessage = (error: unknown): string => {
if (error instanceof Error) {
return error.message;
......
export type paramsHitung = {
kodeObjekPajak: string;
fgPerhitungan: string;
jenisPerhitungan: string;
phBruto: number;
};
export type transformParamsBupotBulananProps = {
status: string;
metode: 'gross' | 'gross-up';
tglBupot: string;
penghasilanKotor: string;
dppPersen: string;
} & paramsHitung;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment