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() { ...@@ -67,6 +67,8 @@ function JumlahPerhitunganForm() {
metode: fgPerhitungan !== '0' ? METODE_POTONG.GROSS_UP : METODE_POTONG.GROSS, // Simplify conditional assignment metode: fgPerhitungan !== '0' ? METODE_POTONG.GROSS_UP : METODE_POTONG.GROSS, // Simplify conditional assignment
}; };
console.log('🚀 ~ handleHitung ~ data:', data);
mutate(data as any); mutate(data as any);
}; };
return ( return (
......
const appRootKey = 'unifikasi'; const appRootKey = 'bupot';
const queryKey = { const queryKey = {
getKodeObjekPajak: (params: any) => [appRootKey, 'kode-objek-pajak', params], getKodeObjekPajak: (params: any) => [appRootKey, 'kode-objek-pajak', params],
......
import { useQuery } from '@tanstack/react-query';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { FG_PDF_STATUS, FG_SIGN_STATUS } from '../constant'; import { FG_PDF_STATUS, FG_SIGN_STATUS } from '../constant';
import { useQuery } from '@tanstack/react-query';
import queryKey from '../constant/queryKey'; import queryKey from '../constant/queryKey';
import type { import type {
TBaseResponseAPI, TBaseResponseAPI,
...@@ -93,7 +93,7 @@ const useGetBulanan = ({ params, ...props }: any) => { ...@@ -93,7 +93,7 @@ const useGetBulanan = ({ params, ...props }: any) => {
const query = useQuery<TBaseResponseAPI<TGetListDataTableDnResult>>({ const query = useQuery<TBaseResponseAPI<TGetListDataTableDnResult>>({
queryKey: queryKey.bulanan.all(params), queryKey: queryKey.bulanan.all(params),
queryFn: async () => { queryFn: async () => {
const response = await bulananApi.getBulanan({ params: normalisPropsParmasGetDn(params) }); const response = await bulananApi.getList({ params: normalisPropsParmasGetDn(params) });
return { return {
...response, ...response,
......
import { useMutation } from '@tanstack/react-query'; import { useMutation, type UseMutationOptions } from '@tanstack/react-query';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import queryKey from '../constant/queryKey';
import type { TPostBulananRequest } from '../types/types';
import bulananApi from '../utils/api'; import bulananApi from '../utils/api';
import type { TPostDnRequest } from '../types/types';
const extractKapFromKodeObjekPajak = (kodeObjekPajak: string) => const extractKapFromKodeObjekPajak = (kodeObjekPajak: string) =>
`4111${kodeObjekPajak.split('-')[0]}`; `4111${kodeObjekPajak.split('-')[0]}`;
const transformParams = ({ isPengganti = false, ...Data }: any): TPostDnRequest => { const transformParams = ({ isPengganti = false, ...Data }: any): TPostBulananRequest => {
const { const {
id, id,
idBupot, idBupot,
...@@ -27,7 +28,6 @@ const transformParams = ({ isPengganti = false, ...Data }: any): TPostDnRequest ...@@ -27,7 +28,6 @@ const transformParams = ({ isPengganti = false, ...Data }: any): TPostDnRequest
tglPemotongan, tglPemotongan,
idTku, idTku,
fgGrossUp, fgGrossUp,
komponen,
alamatDipotong, alamatDipotong,
foreignEmployee, foreignEmployee,
passportNo, passportNo,
...@@ -75,14 +75,15 @@ const transformParams = ({ isPengganti = false, ...Data }: any): TPostDnRequest ...@@ -75,14 +75,15 @@ const transformParams = ({ isPengganti = false, ...Data }: any): TPostDnRequest
}, },
tglPemotongan: tglPemotongan ? dayjs(tglPemotongan).format('DDMMYYYY') : '', tglPemotongan: tglPemotongan ? dayjs(tglPemotongan).format('DDMMYYYY') : '',
fgGrossUp: fgGrossUp ?? 1, fgGrossUp: fgGrossUp ?? 1,
komponen: komponen ?? [],
}; };
}; };
const useSaveBulanan = (props?: any) => const useSaveBulanan = (
props?: Omit<UseMutationOptions<any, Error, any, unknown>, 'mutationKey' | 'mutationFn'>
) =>
useMutation({ useMutation({
mutationKey: ['Save-Dn'], mutationKey: queryKey.bulanan.draft,
mutationFn: (params: any) => bulananApi.saveBulanan(transformParams(params)), mutationFn: (params: any) => bulananApi.save(transformParams(params)),
...props, ...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 = { ...@@ -181,7 +181,7 @@ export type ActionItem = {
disabled?: boolean; disabled?: boolean;
}; };
export type TPostDnRequest = { export type TPostBulananRequest = {
id?: number | null; id?: number | null;
npwpPemotong: string; npwpPemotong: string;
idTku: string; idTku: string;
...@@ -215,8 +215,8 @@ export type TPostDnRequest = { ...@@ -215,8 +215,8 @@ export type TPostDnRequest = {
idBupot?: string; idBupot?: string;
revNo: number; revNo: number;
fgGrossUp: number; fgGrossUp: number;
komponen: Array<{ };
kode: string;
value: string; export type TPortBulananUploadRequest = {
}>; id: string;
}; };
import { fetcher } from 'src/lib/axios';
import type { import type {
BupotRecord, BupotRecord,
TBaseResponseAPI, TBaseResponseAPI,
TGetListDataKOPDnResult, TGetListDataKOPDnResult,
TGetListDataTableDnResult, TGetListDataTableDnResult,
TPostDnRequest, TPortBulananUploadRequest,
TPostBulananRequest,
} from '../types/types'; } from '../types/types';
import bulananClient from './bulananClient';
const bulananApi = () => {}; const bulananApi = () => {};
// API untuk get list table // API untuk get list table
bulananApi.getBulanan = async (config: any) => { bulananApi.getList = async (config: any) => {
const { const response = await fetcher<TBaseResponseAPI<TGetListDataTableDnResult>>([
data: { message, metaPage, data }, 'IF_TXR_028/a0',
status: statusCode, {
} = await bulananClient.get<TBaseResponseAPI<TGetListDataTableDnResult>>('IF_TXR_028/a0', { method: 'GET',
...config, ...config,
}); },
]);
if (statusCode !== 200) { if (!response || response.status !== 'success') {
throw new Error(message); throw new Error(response?.message || 'Failed to fetch bulanan data');
} }
const { message, metaPage, data } = response;
return { total: metaPage ? Number(metaPage.totalRow) : 0, data }; return { total: metaPage ? Number(metaPage.totalRow) : 0, data };
}; };
// ✅ adjust biar bisa terima params // ✅ adjust biar bisa terima params
bulananApi.getKodeObjekPajakBulanan = async (params?: Record<string, any>) => { bulananApi.getKodeObjekPajak = async (params?: Record<string, any>) => {
const response = await bulananClient.get<TBaseResponseAPI<TGetListDataKOPDnResult>>( const response = await fetcher<TBaseResponseAPI<TGetListDataKOPDnResult>>([
'/sandbox/mst_kop_bpu', '/sandbox/mst_kop_bpu',
{ params } // ⬅️ inject ke query string {
); method: 'GET',
params, // ⬅️ inject ke query string
},
]);
const body = response.data; if (!response || response.status !== 'success') {
throw new Error(response?.message || 'Failed to fetch kode objek pajak');
}
return response;
};
if (response.status !== 200 || body.status !== 'success') { bulananApi.save = async (config: TPostBulananRequest) => {
throw new Error(body.message); const response = await fetcher<TBaseResponseAPI<BupotRecord>>([
'/IF_TXR_028/a0',
{
method: 'POST',
data: config,
},
]);
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) => { bulananApi.upload = async (config: TPortBulananUploadRequest) => {
const { const response = await fetcher<TBaseResponseAPI<BupotRecord>>([
data: { message, data }, '/IF_TXR_028/a0/upload',
status: statusCode, {
} = await bulananClient.post<TBaseResponseAPI<BupotRecord>>('/IF_TXR_028/a0', { method: 'POST',
...config, data: config,
}); },
if (statusCode !== 200) { ]);
throw new Error(message);
if (!response || response.status !== 'success') {
throw new Error(response?.message || 'Failed to upload bulanan data');
} }
return data; return response.data;
}; };
export default bulananApi; 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'; ...@@ -10,7 +10,6 @@ import { useMemo, useState } from 'react';
import TableHeaderLabel from 'src/shared/components/TableHeaderLabel'; import TableHeaderLabel from 'src/shared/components/TableHeaderLabel';
import useGetBulanan from '../hooks/useGetBulanan'; import useGetBulanan from '../hooks/useGetBulanan';
import { isEmpty } from 'lodash'; import { isEmpty } from 'lodash';
import { Typography } from '@mui/material';
// import CustomToolbarDn from '../components/customToolbarDn'; // import CustomToolbarDn from '../components/customToolbarDn';
// import CustomToolbar, { CustomFilterButton } from '../components/customToolbarDn2'; // import CustomToolbar, { CustomFilterButton } from '../components/customToolbarDn2';
...@@ -114,8 +113,6 @@ export function BulananListView() { ...@@ -114,8 +113,6 @@ export function BulananListView() {
const totalRows = data?.total || 0; const totalRows = data?.total || 0;
const rows = useMemo(() => data?.data || [], [data?.data]); const rows = useMemo(() => data?.data || [], [data?.data]);
console.log({ data: data.data[0] });
// const handleChange = (event: React.SyntheticEvent, newValue: number) => { // const handleChange = (event: React.SyntheticEvent, newValue: number) => {
// setTabs1(newValue); // setTabs1(newValue);
// }; // };
...@@ -214,9 +211,7 @@ export function BulananListView() { ...@@ -214,9 +211,7 @@ export function BulananListView() {
headerName: 'NPWP', headerName: 'NPWP',
headerAlign: 'center', headerAlign: 'center',
align: 'left', align: 'left',
minWidth: 200, minWidth: 200,
renderCell: (params) => <Typography className="text-[14px] ">{params.value}</Typography>,
}, },
{ {
field: 'namaDipotong', field: 'namaDipotong',
...@@ -350,6 +345,7 @@ export function BulananListView() { ...@@ -350,6 +345,7 @@ export function BulananListView() {
initialState={{ initialState={{
pagination: { paginationModel: { pageSize: 10, page: 0 } }, pagination: { paginationModel: { pageSize: 10, page: 0 } },
}} }}
disableVirtualization
pagination pagination
pageSizeOptions={[5, 10, 15, 25, 50, 100, 250, 500, 750, 100]} pageSizeOptions={[5, 10, 15, 25, 50, 100, 250, 500, 750, 100]}
paginationMode="server" paginationMode="server"
......
...@@ -28,6 +28,7 @@ import { ...@@ -28,6 +28,7 @@ import {
} from '../constant'; } from '../constant';
import { checkCurrentPage } from '../utils/utils'; import { checkCurrentPage } from '../utils/utils';
import useSaveBulanan from '../hooks/useSaveBulanan'; import useSaveBulanan from '../hooks/useSaveBulanan';
import DialogPenandatangan from '../../DialogPenandatangan';
const bulananSchema = z const bulananSchema = z
.object({ .object({
...@@ -75,7 +76,7 @@ const bulananSchema = z ...@@ -75,7 +76,7 @@ const bulananSchema = z
.refine((data) => data.value !== '', { .refine((data) => data.value !== '', {
message: 'PTKP wajib dipilih', message: 'PTKP wajib dipilih',
}), }),
email: z.string().email('Email tidak valid').nullable().optional(), email: z.string().optional(),
jumlahPenghasilan: z.array(z.object({})).optional(), jumlahPenghasilan: z.array(z.object({})).optional(),
jumlahPengurangan: z.array(z.object({})).optional(), jumlahPengurangan: z.array(z.object({})).optional(),
idTku: z idTku: z
...@@ -174,6 +175,7 @@ export const BulananRekamView = () => { ...@@ -174,6 +175,7 @@ export const BulananRekamView = () => {
const [isOpenPanduan, setIsOpenPanduan] = useState<boolean>(false); const [isOpenPanduan, setIsOpenPanduan] = useState<boolean>(false);
const [isCheckedAgreement, setIsCheckedAgreement] = useState<boolean>(false); const [isCheckedAgreement, setIsCheckedAgreement] = useState<boolean>(false);
const [isOpenDialogPenandatangan, setIsOpenDialogPenandatangan] = useState(false);
const actionRekam = checkCurrentPage(pathname); const actionRekam = checkCurrentPage(pathname);
const dataListKOP = useMemo( const dataListKOP = useMemo(
...@@ -248,9 +250,9 @@ export const BulananRekamView = () => { ...@@ -248,9 +250,9 @@ export const BulananRekamView = () => {
statusPtkp: data.ptkp?.value || '', statusPtkp: data.ptkp?.value || '',
kdObjPjk: data.kdObjPjk.value, kdObjPjk: data.kdObjPjk.value,
kdJnsPjk: 'PPh Pasal 21', // Sesuaikan dengan konstanta Anda kdJnsPjk: 'PPh Pasal 21', // Sesuaikan dengan konstanta Anda
jmlBruto: parseInt(data.phBruto || '0', 10), jmlBruto: data.phBruto,
tarif: parseInt(data.tarif || '0', 10), tarif: data.tarif,
pphDipotong: parseInt(data.pph21 || '0', 10), pphDipotong: data.pph21,
fgFasilitas: data.fgFasilitas.value, fgFasilitas: data.fgFasilitas.value,
noDokLainnya: data.skb || '', noDokLainnya: data.skb || '',
fgGrossUp: parseInt(data.fgPerhitungan || '1', 10), fgGrossUp: parseInt(data.fgPerhitungan || '1', 10),
...@@ -261,6 +263,10 @@ export const BulananRekamView = () => { ...@@ -261,6 +263,10 @@ export const BulananRekamView = () => {
await saveBulanan(transformedData); await saveBulanan(transformedData);
}; };
const handleClickUpload = async () => {
setIsOpenDialogPenandatangan(true);
};
const SubmitRekam = async (data: BpuFormData) => { const SubmitRekam = async (data: BpuFormData) => {
const respon = await handleDraft(data); const respon = await handleDraft(data);
console.log({ respon }); console.log({ respon });
...@@ -276,6 +282,7 @@ export const BulananRekamView = () => { ...@@ -276,6 +282,7 @@ export const BulananRekamView = () => {
]; ];
return ( return (
<>
<DashboardContent> <DashboardContent>
<CustomBreadcrumbs <CustomBreadcrumbs
heading="Add Bupot PPh Pasal 21 Bulanan" heading="Add Bupot PPh Pasal 21 Bulanan"
...@@ -337,7 +344,7 @@ export const BulananRekamView = () => { ...@@ -337,7 +344,7 @@ export const BulananRekamView = () => {
<LoadingButton <LoadingButton
type="button" type="button"
disabled={!isCheckedAgreement} disabled={!isCheckedAgreement}
onClick={methods.handleSubmit(SubmitRekam)} onClick={methods.handleSubmit(handleClickUpload)}
loading={isSaving} loading={isSaving}
variant="contained" variant="contained"
sx={{ background: '#143B88' }} sx={{ background: '#143B88' }}
...@@ -354,5 +361,12 @@ export const BulananRekamView = () => { ...@@ -354,5 +361,12 @@ export const BulananRekamView = () => {
</Grid> </Grid>
</Grid> </Grid>
</DashboardContent> </DashboardContent>
<DialogPenandatangan
isOpen={isOpenDialogPenandatangan}
onClose={() => {
setIsOpenDialogPenandatangan(false);
}}
/>
</>
); );
}; };
...@@ -4,7 +4,13 @@ import { ...@@ -4,7 +4,13 @@ import {
KODE_OBJEK_PAJAK, KODE_OBJEK_PAJAK,
PERHITUNGAN_BUPOT21, PERHITUNGAN_BUPOT21,
} from './bupot-bulanan/constant'; } from './bupot-bulanan/constant';
import type { paramsHitung } from './type';
export type paramsHitung = {
kodeObjekPajak: string;
fgPerhitungan: string;
jenisPerhitungan: string;
phBruto: number;
};
export const checkPerhitunganBupot21 = ({ export const checkPerhitunganBupot21 = ({
kodeObjekPajak, kodeObjekPajak,
......
import { import {
useMutation, useMutation,
type UseMutationResult,
type UseMutationOptions, type UseMutationOptions,
type UseMutationResult,
} from '@tanstack/react-query'; } from '@tanstack/react-query';
import type { AxiosError } from 'axios'; import type { AxiosError } from 'axios';
import { endpoints, fetcherHitung } from 'src/lib/axios-ctas-box';
import { fetcherHitung, endpoints } from 'src/lib/axios-ctas-box';
import type { transformParamsBupotBulananProps } from './type';
import { checkPerhitunganBupot21 } from './helper';
import { PERHITUNGAN_BUPOT21 } from './bupot-bulanan/constant'; import { PERHITUNGAN_BUPOT21 } from './bupot-bulanan/constant';
import { checkPerhitunganBupot21, type paramsHitung } from './helper';
// ======================================== // ========================================
// Types // Types
// ======================================== // ========================================
export type transformParamsBupotBulananProps = {
status: string;
metode: 'gross' | 'gross-up';
tglBupot: string;
penghasilanKotor: string;
dppPersen: string;
} & paramsHitung;
interface ApiResponse<T> { interface ApiResponse<T> {
code: number; code: number;
status: string; status: string;
...@@ -153,30 +159,6 @@ const handleHitungBulanan = async ( ...@@ -153,30 +159,6 @@ const handleHitungBulanan = async (
// React Query Hook // 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( export function useHitungBulanan(
props?: MutationProps props?: MutationProps
): UseMutationResult<ApiResponseBulanan, AxiosError, transformParamsBupotBulananProps> { ): UseMutationResult<ApiResponseBulanan, AxiosError, transformParamsBupotBulananProps> {
...@@ -191,9 +173,6 @@ export function useHitungBulanan( ...@@ -191,9 +173,6 @@ export function useHitungBulanan(
// Error Handler Utility (Optional) // Error Handler Utility (Optional)
// ======================================== // ========================================
/**
* Helper untuk handle error dari mutation
*/
export const getHitungBulananErrorMessage = (error: unknown): string => { export const getHitungBulananErrorMessage = (error: unknown): string => {
if (error instanceof Error) { if (error instanceof Error) {
return error.message; 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