Commit cf7f44f4 authored by Rais Aryaguna's avatar Rais Aryaguna

feat: Add hooks and views for managing Bulanan Bupot

- Implemented `useGetBulanan` hook for fetching Bulanan data.
- Created `usePphDipotong` hook for handling PPh calculations.
- Added `useSaveBulanan` hook for saving Bulanan data.
- Defined types in `types.ts` for API responses and requests.
- Developed API utility functions in `api.tsx` for fetching and saving data.
- Created `bulananClient.tsx` for Axios instance with interceptors.
- Added utility functions in `utils.tsx` for date and pagination handling.
- Built `BulananListView` for displaying Bulanan records in a data grid.
- Developed `BulananRekamView` for recording new Bulanan entries with form validation.
- Organized view exports in `index.ts` for easier imports.
parent 33a1b7e3
import type { SWRConfiguration } from 'swr';
import useSWR from 'swr';
import { useMemo } from 'react';
import { fetcher, endpoints } from 'src/lib/axios-ctas-box';
// ----------------------------------------------------------------------
const swrOptions: SWRConfiguration = {
revalidateIfStale: false,
revalidateOnFocus: false,
revalidateOnReconnect: false,
};
// ----------------------------------------------------------------------
export function useKodeNegara() {
const url = endpoints.masterData.kodeNegara;
const { data, isLoading, error, isValidating } = useSWR<{
data: { kode: string; nama: string }[];
}>(url, fetcher, {
...swrOptions,
});
const memoizedValue = useMemo(
() => ({
kodeNegara: data?.data || [],
kodeNegaraLoading: isLoading,
kodeNegaraError: error,
kodeNegaraValidating: isValidating,
kodeNegaraEmpty: !isLoading && !data?.data.length,
}),
[data, error, isLoading, isValidating]
);
return memoizedValue;
}
// const getServices = async (params) => {
// const response = await axiosBupot.get('/mst_services', {
// params,
// });
// const body = response.data;
// if (
// body.status === 'fail' ||
// body.status === 'error' ||
// body.status === '0' ||
// response.status !== 200
// ) {
// throw new Error(body.message || 'System tidak dapat memenuhi permintaan, coba beberapa saat lagi');
// }
// return body.data;
// };
// const getGoods = async (params) => {
// const response = await axiosBupot.get('/mst_goods', {
// params,
// });
// const body = response.data;
// if (
// body.status === 'fail' ||
// body.status === 'error' ||
// body.status === '0' ||
// response.status !== 200
// ) {
// throw new Error(body.message || 'System tidak dapat memenuhi permintaan, coba beberapa saat lagi');
// }
// return body.data;
// };
// const getSatuan = async (params) => {
// const response = await axiosBupot.get('/mst_satuan', {
// params,
// });
// const body = response.data;
// if (
// body.status === 'fail' ||
// body.status === 'error' ||
// body.status === '0' ||
// response.status !== 200
// ) {
// throw new Error(body.message || 'System tidak dapat memenuhi permintaan, coba beberapa saat lagi');
// }
// return body.data;
// };
// const getPenandatangan = async () => {
// const {
// data: { status: statusRes, message, data },
// } = await axiosBupot.get('/signer');
// if (statusRes === 'error') {
// throw new Error(message);
// }
// return data;
// };
// const getKodeObjekPajakBpnr = async (params) => {
// const response = await axiosBupot.get('/mst_kop_bpnr', {
// params,
// });
// const body = response.data;
// if (
// body.status === 'fail' ||
// body.status === 'error' ||
// body.status === '0' ||
// response.status !== 200
// ) {
// throw new Error(body.message || 'System tidak dapat memenuhi permintaan, coba beberapa saat lagi');
// }
// return body.data;
// };
// const getKodeObjekPajakBpu = async (params) => {
// const response = await axiosBupot.get('/mst_kop_bpu', {
// params,
// });
// const body = response.data;
// if (
// body.status === 'fail' ||
// body.status === 'error' ||
// body.status === '0' ||
// response.status !== 200
// ) {
// throw new Error(body.message || 'System tidak dapat memenuhi permintaan, coba beberapa saat lagi');
// }
// return body.data;
// };
// const getKodeObjekPajakBpsp = async (params) => {
// const response = await axiosBupot.get('/mst_kop_bpsp', {
// params,
// });
// const body = response.data;
// if (
// body.status === 'fail' ||
// body.status === 'error' ||
// body.status === '0' ||
// response.status !== 200
// ) {
// throw new Error(body.message || 'System tidak dapat memenuhi permintaan, coba beberapa saat lagi');
// }
// return body.data;
// };
// const getAllKodeObjekPajak = async (params) => {
// const response = await axiosBupot.get('/mst_kop_all', {
// params,
// });
// const body = response.data;
// if (
// body.status === 'fail' ||
// body.status === 'error' ||
// body.status === '0' ||
// response.status !== 200
// ) {
// throw new Error(body.message || 'System tidak dapat memenuhi permintaan, coba beberapa saat lagi');
// }
// return body;
// };
// const getKeteranganTambahan = async (params) => {
// const response = await axiosBupot.get('/mst_faktur_keterangan', {
// params,
// });
// const body = response.data;
// if (
// body.status === 'fail' ||
// body.status === 'error' ||
// body.status === '0' ||
// response.status !== 200
// ) {
// throw new Error(body.message || 'System tidak dapat memenuhi permintaan, coba beberapa saat lagi');
// }
// return body.data;
// };
// const getIdKeteranganTambahan = async (params) => {
// const response = await axiosBupot.get('/mst_faktur_idtambahan', {
// params,
// });
// const body = response.data;
// if (
// body.status === 'fail' ||
// body.status === 'error' ||
// body.status === '0' ||
// response.status !== 200
// ) {
// throw new Error(body.message || 'System tidak dapat memenuhi permintaan, coba beberapa saat lagi');
// }
// return body.data;
// };
......@@ -26,6 +26,8 @@ export type ConfigValue = {
amplify: { userPoolId: string; userPoolWebClientId: string; region: string };
auth0: { clientId: string; domain: string; callbackUrl: string };
supabase: { url: string; key: string };
nodesandbox: { api: string };
apiJava: { api: string };
};
// ----------------------------------------------------------------------
......@@ -79,4 +81,17 @@ export const CONFIG: ConfigValue = {
url: import.meta.env.VITE_SUPABASE_URL ?? '',
key: import.meta.env.VITE_SUPABASE_ANON_KEY ?? '',
},
/**
* nodesandbox
*/
nodesandbox: {
api: import.meta.env.VITE_HOST_API ?? '',
},
/**
* java
*/
apiJava: {
api: import.meta.env.VITE_HOST_API ?? '',
},
};
import type { AxiosRequestConfig, AxiosInstance } from 'axios';
import axios from 'axios';
import { JWT_STORAGE_KEY } from 'src/auth/context/jwt';
import { CONFIG } from 'src/global-config';
const API_CONFIGS = {
nodesandbox: {
baseURL: CONFIG.nodesandbox.api, // misal: https://api.yourdomain.com
name: 'nodesandbox API',
},
} as const;
const createAxiosInstance = (baseURL: string, name: string): AxiosInstance => {
const instance = axios.create({
baseURL,
headers: {
'Content-Type': 'application/json',
},
});
// Request Interceptor - Tambahkan token jika diperlukan
instance.interceptors.request.use(
(config) => {
const accessToken = localStorage.getItem(JWT_STORAGE_KEY);
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Response Interceptor - Handle errors
instance.interceptors.response.use(
(response) => response,
(error) => {
const message = error?.response?.data?.message || error?.message || 'Something went wrong!';
console.error(`[${name}] Axios error:`, message);
return Promise.reject(new Error(message));
}
);
return instance;
};
export const axiosnodesandbox = createAxiosInstance(
API_CONFIGS.nodesandbox.baseURL,
API_CONFIGS.nodesandbox.name
);
export default axiosnodesandbox;
type FetcherArgs = string | [string, AxiosRequestConfig & { method?: string }];
export const fetcher = async <T = unknown>(args: FetcherArgs): Promise<T> => {
try {
const [url, config = {}] = Array.isArray(args) ? args : [args, {}];
const { method = 'GET', ...restConfig } = config;
const res = await axiosnodesandbox.request<T>({
url,
method,
...restConfig,
});
return res.data;
} catch (error) {
console.error('[sandbox API] Fetcher failed:', error);
throw error;
}
};
export const endpoints = {
pph21: {
bulanan: {
list: '/IF_TXR_028/a0',
delete: '/IF_TXR_028/a0/delete',
upload: '/IF_TXR_028/a0/upload',
canceled: '/IF_TXR_028/a0/batal',
},
},
masterData: {
kodeNegara: '/sandbox/mst_negara',
services: '/sandbox/mst_services',
goods: '/sandbox/mst_goods',
satuan: '/sandbox/mst_satuan',
signer: '/sandbox/signer',
kop_bpnr: '/sandbox/mst_kop_bpnr',
kop_bpu: '/sandbox/mst_kop_bpu',
kop_bpsp: '/sandbox/mst_kop_bpsp',
kop_all: '/sandbox/mst_kop_all',
faktur_keterangan: '/sandbox/mst_faktur_kode',
faktur_idtambahan: '/sandbox/mst_faktur_keterangan',
},
} as const;
......@@ -62,7 +62,7 @@ export const endpoints = {
calendar: '/api/calendar',
auth: {
// me: '/api/auth/me',
me:'/sandbox/userinfo',
me: '/sandbox/userinfo',
signIn: '/sandbox/login',
signUp: '/api/auth/sign-up',
},
......
import { CONFIG } from 'src/global-config';
import { BulananListView } from 'src/sections/bupot-21-26/bupot-bulanan/view';
const metadata = { title: `E-Bupot 21/26- ${CONFIG.appName}` };
......@@ -7,7 +8,7 @@ export default function Page() {
<>
<title>{metadata.title}</title>
<p>Bupot Bulanan</p>
<BulananListView />
</>
);
}
import { CONFIG } from 'src/global-config';
import { BulananRekamView } from 'src/sections/bupot-21-26/bupot-bulanan/view';
const metadata = { title: `E-Bupot Bulanan Rekam- ${CONFIG.appName}` };
export default function Page() {
return (
<>
<title>{metadata.title}</title>
<BulananRekamView />
</>
);
}
......@@ -105,10 +105,13 @@ export const paths = {
sspNew: `${ROOTS.UNIFIKASI}/ssp/new`,
dokumenDipersamakanNew: `${ROOTS.UNIFIKASI}/dokumen-dipersamakan/new`,
},
// Bupot 21/26
pph21: {
root: ROOTS.PPH21,
bulanan: `${ROOTS.PPH21}/bulanan`,
detailsBulanan: (id: string) => `/bulanan/${id}`,
bulananRekam: `${ROOTS.PPH21}/bulanan/rekam`,
bulananUbah: (id: string) => `/bulanan/${id}/ubah`,
bulananPengganti: (id: string) => `/bulanan/${id}/pengganti`,
bupotFinal: `${ROOTS.PPH21}/bupot-final`,
detailsBupotFinal: (id: string) => `/bupot-final/${id}`,
tahuan: `${ROOTS.PPH21}/tahunan`,
......
......@@ -57,6 +57,7 @@ const AccountChangePasswordPage = lazy(
// Bupot 21/26
const OverviewBupotBulananPage = lazy(() => import('src/pages/pph21/bupotBulanan'));
const OverviewBupotBulananRekamPage = lazy(() => import('src/pages/pph21/bupotBulananRekam'));
const OverviewBupotFinalTdkFinalPage = lazy(() => import('src/pages/pph21/bupotFinaltidakFinal'));
const OverviewBupotA1Page = lazy(() => import('src/pages/pph21/bupoTahunanA1'));
const OverviewBupotPasal26Page = lazy(() => import('src/pages/pph21/bupotPasal26'));
......@@ -137,6 +138,9 @@ export const dashboardRoutes: RouteObject[] = [
children: [
{ index: true, element: <OverviewBupotBulananPage /> },
{ path: 'bulanan', element: <OverviewBupotBulananPage /> },
{ path: 'bulanan/rekam', element: <OverviewBupotBulananRekamPage /> },
{ path: 'bulanan/:id/ubah', element: <OverviewBupotBulananRekamPage /> },
{ path: 'bulanan/:id/pengganti', element: <OverviewBupotBulananRekamPage /> },
{ path: 'bupot-final', element: <OverviewBupotFinalTdkFinalPage /> },
{ path: 'tahunan', element: <OverviewBupotA1Page /> },
{ path: 'bupot-26', element: <OverviewBupotPasal26Page /> },
......
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Grid from '@mui/material/Grid';
import dayjs from 'dayjs';
import { useEffect, useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { useParams } from 'react-router';
import { useKodeNegara } from 'src/actions/master-data';
import { Field } from 'src/components/hook-form';
type IdentitasProps = {
isPengganti?: boolean;
};
const Identitas = ({ isPengganti }: IdentitasProps) => {
const { dnId } = useParams();
const { setValue, watch } = useFormContext();
const tanggalPemotongan = watch('tglPemotongan');
const [jumlahKeterangan, setJumlahKeterangan] = useState<number>(0);
const maxKeterangan = 5;
const handleTambah = () => {
if (jumlahKeterangan < maxKeterangan) {
setJumlahKeterangan(jumlahKeterangan + 1);
}
};
const handleHapus = () => {
if (jumlahKeterangan > 0) {
const newCount = jumlahKeterangan - 1;
setJumlahKeterangan(newCount);
setValue(`keterangan${newCount + 1}`, null);
}
};
// auto set thnPajak dan msPajak berdasarkan tanggalPemotongan
useEffect(() => {
if (tanggalPemotongan) {
const date = dayjs(tanggalPemotongan);
setValue('thnPajak', date.format('YYYY'));
setValue('msPajak', date.format('MM'));
} else {
setValue('thnPajak', '');
setValue('msPajak', '');
}
}, [tanggalPemotongan, setValue]);
const { kodeNegara } = useKodeNegara();
return (
<>
<Grid container rowSpacing={2} alignItems="center" columnSpacing={2} sx={{ mb: 4 }}>
<Grid size={{ md: 6 }}>
<Field.DatePicker
name="tglPemotongan"
label="Tanggal Pemotongan"
format="DD/MM/YYYY"
maxDate={dayjs()}
/>
</Grid>
<Grid size={{ md: 3 }}>
<Field.DatePicker name="thnPajak" label="Tahun Pajak" view="year" format="YYYY" />
</Grid>
<Grid size={{ md: 3 }}>
<Field.DatePicker name="msPajak" label="Masa Pajak" view="month" format="MM" />
</Grid>
{/* NPWP dengan onChange langsung */}
<Grid size={{ md: 6 }}>
<Field.Text
name="idDipotong"
label="NPWP"
onChange={(e) => {
const value = e.target.value.replace(/\D/g, '').slice(0, 16); // hanya angka, max 16
setValue('idDipotong', value, { shouldValidate: true, shouldDirty: true });
setValue('nitku', value.length === 16 ? value + '000000' : value, {
shouldValidate: true,
shouldDirty: true,
});
}}
/>
</Grid>
<Grid size={{ md: 6 }}>
<Field.Text name="namaDipotong" label="Nama" />
</Grid>
<Grid size={{ md: 12 }}>
<Field.Text name="alamatDipotong" label="Alamat" />
</Grid>
<Grid size={{ md: 6 }}>
<Field.Text name="email" label="Email (optional)" />
</Grid>
<Grid size={{ md: 6 }}>
<Field.Text name="posisiJabatan" label="Jabatan" />
</Grid>
<Grid size={{ md: 3 }} alignSelf="center">
<Field.Checkbox name="fgKaryawanAsing" label="Status Karyawan Asing" />
</Grid>
<Grid size={{ md: 3 }}>
<Field.Autocomplete
name="kodeNegara"
label="Negara"
options={kodeNegara.map((val) => ({ label: val.nama, ...val }))}
/>
</Grid>
<Grid size={{ md: 6 }}>
<Field.Text name="passport" label="Paspor" />
</Grid>
</Grid>
{/* Tambah / Hapus Keterangan */}
<Box sx={{ display: 'flex', gap: 2, mb: 3 }}>
<Box
sx={{
borderRadius: '18px',
border: jumlahKeterangan >= maxKeterangan ? '1px solid #eee' : '1px solid #2e7d3280',
color: jumlahKeterangan >= maxKeterangan ? '#eee' : '#2e7d3280',
p: '0px 10px',
}}
>
<Button disabled={jumlahKeterangan >= maxKeterangan} onClick={handleTambah}>
Tambah Keterangan
</Button>
</Box>
<Box
sx={{
borderRadius: '18px',
border: jumlahKeterangan === 0 ? '1px solid #eee' : '1px solid #f44336',
color: jumlahKeterangan === 0 ? '#eee' : '#f44336',
p: '0px 10px',
}}
>
<Button disabled={jumlahKeterangan === 0} onClick={handleHapus}>
Hapus Keterangan
</Button>
</Box>
</Box>
<Box sx={{ mb: 3 }}>
{Array.from({ length: jumlahKeterangan }).map((_, i) => (
<Grid size={{ md: 12 }} key={i}>
<Field.Text
sx={{ mb: 2 }}
name={`keterangan${i + 1}`}
label={`Keterangan Tambahan ${i + 1}`}
/>
</Grid>
))}
</Box>
</>
);
};
export default Identitas;
import { Grid } from '@mui/material';
import { Field } from 'src/components/hook-form';
import {
FG_FASILITAS_PPH_21,
FG_PERHITUNGAN,
FG_PERHITUNGAN_TEXT,
KODE_OBJEK_PAJAK,
PTKP,
PTKP_TEXT,
PTKP_TITLE,
} from '../../constant';
import { useMemo } from 'react';
import { RHFNumeric } from 'src/components/hook-form/rhf-numeric';
import { useFormContext } from 'react-hook-form';
import { LoadingButton } from '@mui/lab';
import { CalculateRounded } from '@mui/icons-material';
const fgPerhitunganOptions = Object.values(FG_PERHITUNGAN).map((value) => ({
value,
label: FG_PERHITUNGAN_TEXT[value],
}));
function JumlahPerhitunganForm() {
const { watch } = useFormContext();
const fgFasilitas = watch('fgFasilitas');
const ptkpOptions = useMemo(
() =>
Object.entries(PTKP)
.map(([key, value]) => ({ value, label: PTKP_TEXT[value] }))
.filter((option) => !option.value.includes(PTKP_TITLE.HB)),
[]
);
return (
<Grid container rowSpacing={2} columnSpacing={2} sx={{ my: 3 }}>
<Grid size={{ md: 3 }}>
<Field.RadioGroup
row
name="fgPerhitungan"
label="Metode Pemotongan"
options={fgPerhitunganOptions.filter((a) => a.value !== FG_PERHITUNGAN.MIXED)}
/>
</Grid>
<Grid size={{ md: 9 }}>
<Field.Autocomplete name="ptkp" label="Status PTKP" options={ptkpOptions} />
</Grid>
<Grid size={{ md: 6 }}>
<RHFNumeric name="phBruto" label="Jumlah Penghasilan (Rp)" />
</Grid>
<Grid size={{ md: 6 }}>
<RHFNumeric name="tunjanganPPh" label="Tunjangan PPh 21 (Rp)" />
</Grid>
<Grid size={{ md: 5 }}>
<RHFNumeric
name="tarif"
label="Tarif (%)"
allowDecimalValue
maxValue={100}
readOnly={
![
KODE_OBJEK_PAJAK.FINAL_38,
KODE_OBJEK_PAJAK.FINAL_99,
KODE_OBJEK_PAJAK.TIDAK_FINAL_99,
].includes(watch('kodeObjekPajak')) &&
![FG_FASILITAS_PPH_21.FASILITAS_LAINNYA].includes(watch('fgFasilitas'))
}
/>
</Grid>
<Grid size={{ md: 5 }}>
<RHFNumeric
name="pph21"
label="PPh Pasal 21"
readOnly={![KODE_OBJEK_PAJAK.FINAL_38].includes(watch('kodeObjekPajak'))}
/>
</Grid>
<Grid size={{ md: 2 }} alignSelf="center">
<LoadingButton
variant="contained"
fullWidth
size="large"
color="primary"
// onClick={handleHitung}
// loading={hitung.isLoading}
startIcon={<CalculateRounded />}
>
Hitung
</LoadingButton>
</Grid>
</Grid>
);
}
export default JumlahPerhitunganForm;
import { ChevronRightRounded, CloseRounded } from '@mui/icons-material';
import { Box, Button, Card, CardContent, CardHeader, IconButton, Typography } from '@mui/material';
import { m } from 'framer-motion';
import type { FC } from 'react';
import { PANDUAN_REKAM_DN } from '../../constant';
interface PanduanDnRekamProps {
handleOpen: () => void;
isOpen: boolean;
}
const PanduanDnRekam: FC<PanduanDnRekamProps> = ({ handleOpen, isOpen }) => (
<Box position="sticky">
{/* Tombol toggle */}
<Box
height="100%"
display={isOpen ? 'none' : 'flex'}
justifyContent="center"
alignItems="center"
>
<Button
variant="contained"
sx={{
height: 'fit-content',
right: 0,
borderRadius: 0,
minWidth: 35,
pt: 3,
pb: 3,
fontWeight: 'bold',
fontSize: 16,
backgroundColor: '#143B88',
}}
size="small"
onClick={handleOpen}
>
<span
style={{
writingMode: 'vertical-rl',
transform: 'rotate(180deg)',
display: 'flex',
alignItems: 'center',
}}
>
Panduan Penggunaan
<ChevronRightRounded sx={{ fontSize: 30 }} />
</span>
</Button>
</Box>
{/* Konten panduan */}
{isOpen && (
<m.div
initial={{ x: 20, opacity: 0 }}
animate={{ x: 0, opacity: 1, transition: { delay: 0.2 } }}
>
<Card>
<CardHeader
avatar={
<img src="/assets/icon_panduan_penggunaan_1.svg" alt="Panduan" loading="lazy" />
}
sx={{
backgroundColor: '#123375',
color: '#FFFFFF',
padding: '16px',
'& .MuiCardHeader-title': {
fontSize: 18,
},
}}
action={
<IconButton aria-label="close" onClick={handleOpen} sx={{ color: 'white' }}>
<CloseRounded />
</IconButton>
}
title="Panduan Penggunaan"
/>
<CardContent
sx={{
maxHeight: 300,
overflow: 'auto',
'&::-webkit-scrollbar': { width: 6 },
'&::-webkit-scrollbar-track': { backgroundColor: '#f0f0f0', borderRadius: 8 },
'&::-webkit-scrollbar-thumb': { backgroundColor: '#123375', borderRadius: 8 },
'&::-webkit-scrollbar-thumb:hover': { backgroundColor: '#0d2858' },
scrollbarWidth: 'thin',
scrollbarColor: '#123375 #f0f0f0',
}}
>
{/* Deskripsi Form */}
<Typography variant="body2" sx={{ mb: 2, whiteSpace: 'pre-line' }}>
<span style={{ fontWeight: 600 }}>Deskripsi Form:</span>
<br />
{PANDUAN_REKAM_DN.description.intro}
</Typography>
<Typography variant="body2" sx={{}}>
{PANDUAN_REKAM_DN.description.textList}
</Typography>
<Box component="ol" sx={{ pl: 3, mb: 2 }}>
{PANDUAN_REKAM_DN.description.list.map((item, idx) => (
<Typography key={idx} variant="body2" component="li">
{item}
</Typography>
))}
</Box>
<Typography variant="body2" sx={{ mb: 2 }}>
{PANDUAN_REKAM_DN.description.closing}
</Typography>
{/* Bagian-bagian */}
{PANDUAN_REKAM_DN.sections.map((section, i) => (
<Box key={i} sx={{ mb: 2 }}>
<Typography
variant="body2"
sx={{ fontWeight: 'bold', fontSize: '0.95rem', mb: 0.5 }}
>
{section.title}
</Typography>
<Box component="ul" sx={{ pl: 2, listStyle: 'disc' }}>
{section.items.map((item, idx) => (
<Box key={idx} component="li" sx={{ mb: 0.5 }}>
<Typography variant="body2" component="span">
{item.text}
</Typography>
{item.subItems.length > 0 && (
<Box component="ol" sx={{ pl: 3, listStyle: 'decimal' }}>
{item.subItems.map((sub, subIdx) => (
<Typography key={subIdx} variant="body2" component="li">
{sub}
</Typography>
))}
</Box>
)}
</Box>
))}
</Box>
</Box>
))}
</CardContent>
</Card>
</m.div>
)}
</Box>
);
export default PanduanDnRekam;
import Divider from '@mui/material/Divider';
import Grid from '@mui/material/Grid';
import { useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
import { Field } from 'src/components/hook-form';
import { FG_FASILITAS_PPH_21, FG_FASILITAS_PPH_21_TEXT } from '../../constant';
type PPHDipotongProps = {
kodeObjectPajak: {
value: string;
label: string;
}[];
};
const PerhitunganPPhPasal21 = ({ kodeObjectPajak }: PPHDipotongProps) => {
const { watch } = useFormContext();
const fgFasilitas = watch('fgFasilitas');
const fgFasilitasOptions = useMemo(
() =>
[
FG_FASILITAS_PPH_21.DTP,
FG_FASILITAS_PPH_21.FASILITAS_LAINNYA,
FG_FASILITAS_PPH_21.TANPA_FASILITAS,
].map((value) => ({
value,
label: FG_FASILITAS_PPH_21_TEXT[value],
})),
[]
);
return (
<Grid container rowSpacing={2} columnSpacing={2}>
{/* Divider */}
<Grid size={{ md: 12 }}>
<Divider sx={{ fontWeight: 'bold' }} textAlign="left">
Perhitungan PPh Pasal 21
</Divider>
</Grid>
{/* Kode objek pajak */}
<Grid size={{ md: 12 }}>
<Field.Autocomplete name="kdObjPjk" label="Kode Objek Pajak" options={kodeObjectPajak} />
</Grid>
{/* Fasilitas */}
<Grid size={{ md: 6 }}>
<Field.Autocomplete name="fgFasilitas" label="Fasilitas" options={fgFasilitasOptions} />
</Grid>
{/* Dokumen lainnya */}
<Grid size={{ md: 6 }}>
<Field.Text
name="noDokLainnya"
label="Nomor Dokumen Lainnya"
disabled={['9', ''].includes(fgFasilitas)}
sx={{ '& .MuiInputBase-root.Mui-disabled': { backgroundColor: '#f6f6f6' } }}
/>
</Grid>
</Grid>
);
};
export default PerhitunganPPhPasal21;
// import {
// FG_KIRIM_EMAIL as EFAKTUR_FG_KIRIM_EMAIL,
// FG_KIRIM_EMAIL_TEXT as EFAKTUR_FG_KIRIM_EMAIL_TEXT,
// } from '@pjap/efaktur/constants/efakturConstant';
export const FG_FASILITAS = {
YES: '1',
NO: '0',
BUPOT26: '2',
};
export const FG_PERHITUNGAN = {
GROSS: '0',
GROSS_UP: '1',
MIXED: '2',
};
export const FG_PERHITUNGAN_TEXT = {
[FG_PERHITUNGAN.GROSS]: 'Gross',
[FG_PERHITUNGAN.GROSS_UP]: 'Gross Up',
[FG_PERHITUNGAN.MIXED]: 'Mixed',
};
export const PPH_21_AKTIF = {
YES: 1,
NO: 0,
};
// export const FG_KIRIM_EMAIL = EFAKTUR_FG_KIRIM_EMAIL;
// export const FG_KIRIM_EMAIL_TEXT = EFAKTUR_FG_KIRIM_EMAIL_TEXT;
export const IDENTITAS = {
NPWP: '0',
NIK: '1',
PASPOR: '2',
NPWP16: '3',
NITKU: '4',
};
export const FG_ID_DIPOTONG = {
NPWP: '0',
NIK: '1',
NPWP26: '2',
NPWP16: '3',
NITKU: '4',
};
export const TUJUAN = {
PEMUTAKHIRAN: '0',
LIST: '1',
};
export const TYPE_REFERENSI_PEGAWAI = {
PENERIMA: 0,
PEGAWAI: 1,
};
export const IDENTITAS_TEXT = {
[IDENTITAS.NPWP]: 'NPWP',
[IDENTITAS.NIK]: 'NIK',
[IDENTITAS.PASPOR]: 'Paspor',
[IDENTITAS.NITKU]: 'NITKU',
[IDENTITAS.NPWP16]: 'NPWP16',
};
export const TUJUAN_TEXT = {
[TUJUAN.PEMUTAKHIRAN]: 'Validasi NITKU',
[TUJUAN.LIST]: 'List NITKU',
};
export const KODE_OBJEK_PAJAK = {
BULANAN_01: '21-100-01',
BULANAN_02: '21-100-02',
BULANAN_03: '21-100-32',
TIDAK_FINAL_03: '21-100-03',
TIDAK_FINAL_04: '21-100-04',
TIDAK_FINAL_05: '21-100-05',
TIDAK_FINAL_06: '21-100-06',
TIDAK_FINAL_07: '21-100-07',
TIDAK_FINAL_09: '21-100-09',
TIDAK_FINAL_10: '21-100-10',
TIDAK_FINAL_11: '21-100-11',
TIDAK_FINAL_12: '21-100-12',
TIDAK_FINAL_13: '21-100-13',
TIDAK_FINAL_99: '21-100-99',
FINAL_01: '21-401-01',
FINAL_02: '21-401-02',
FINAL_99: '21-499-99',
FINAL_38: '21-100-38',
BUPOT_26: '27-100-99',
};
export const KODE_OBJEK_PAJAK_TEXT = {
[KODE_OBJEK_PAJAK.BULANAN_01]: 'Pegawai Tetap',
[KODE_OBJEK_PAJAK.BULANAN_02]: 'Penerima Pensiun Berkala',
[KODE_OBJEK_PAJAK.BULANAN_03]:
'Penghasilan yang diterima atau Diperoleh oleh Pegawai tetap yang menerima fasilitas di daerah tertentu',
[KODE_OBJEK_PAJAK.TIDAK_FINAL_03]: 'Pegawai Tidak Tetap',
[KODE_OBJEK_PAJAK.TIDAK_FINAL_04]: 'Distributor Pemasaran Berjenjang',
[KODE_OBJEK_PAJAK.TIDAK_FINAL_05]: 'Agen Asuransi',
[KODE_OBJEK_PAJAK.TIDAK_FINAL_06]: 'Penjaja Barang Dagangan',
[KODE_OBJEK_PAJAK.TIDAK_FINAL_07]: 'Tenaga Ahli',
[KODE_OBJEK_PAJAK.TIDAK_FINAL_09]: 'Bukan Pegawai Lainnya',
[KODE_OBJEK_PAJAK.TIDAK_FINAL_10]:
'Anggota Dewan Komisaris atau Dewan Pengawas yang Menerima Imbalan Secara Tidak Teratur',
[KODE_OBJEK_PAJAK.TIDAK_FINAL_11]:
'Mantan Pegawai yang Menerima Jasa Produksi, Tantiem, Bonus atau Imbalan Kepada Mantan Pegawai',
[KODE_OBJEK_PAJAK.TIDAK_FINAL_12]: 'Pegawai yang Melakukan Penarikan Uang Pensiun',
[KODE_OBJEK_PAJAK.TIDAK_FINAL_13]: 'Peserta Kegiatan',
[KODE_OBJEK_PAJAK.TIDAK_FINAL_99]:
'Penerima Penghasilan yang Dipotong PPh Pasal 21 Tidak Final Lainnya',
[KODE_OBJEK_PAJAK.FINAL_01]: 'Uang Pesangon yang Dibayarkan Sekaligus',
[KODE_OBJEK_PAJAK.FINAL_02]:
'Penerima Uang Manfaat Pensiun, Tunjangan Hari Tua, atau Jaminan Hari Tua yang Dibayarkan Sekaligus',
[KODE_OBJEK_PAJAK.FINAL_99]: 'Penerima Penghasilan yang Dipotong PPh Pasal 21 Final Lainnya',
[KODE_OBJEK_PAJAK.BUPOT_26]:
'Pegawai/Pemberi Jasa/Peserta Kegiatan/Penerima Pensiun Berkala Sebagai Wajib Pajak Luar Negeri',
};
export const PTKP = {
TK_0: 'TK/0',
TK_1: 'TK/1',
TK_2: 'TK/2',
TK_3: 'TK/3',
K_0: 'K/0',
K_1: 'K/1',
K_2: 'K/2',
K_3: 'K/3',
HB_0: 'HB/0',
HB_1: 'HB/1',
HB_2: 'HB/2',
HB_3: 'HB/3',
};
export const PTKP_TEXT = {
[PTKP.TK_0]: `(${PTKP.TK_0}) - 54.000.000`,
[PTKP.TK_1]: `(${PTKP.TK_1}) - 58.500.000`,
[PTKP.TK_2]: `(${PTKP.TK_2}) - 63.000.000`,
[PTKP.TK_3]: `(${PTKP.TK_3}) - 67.500.000`,
[PTKP.K_0]: `(${PTKP.K_0}) - 58.500.000`,
[PTKP.K_1]: `(${PTKP.K_1}) - 63.000.000`,
[PTKP.K_2]: `(${PTKP.K_2}) - 67.500.000`,
[PTKP.K_3]: `(${PTKP.K_3}) - 72.000.000`,
[PTKP.HB_0]: `(${PTKP.HB_0}) - 54.000.000`,
[PTKP.HB_1]: `(${PTKP.HB_1}) - 58.500.000`,
[PTKP.HB_2]: `(${PTKP.HB_2}) - 63.000.000`,
[PTKP.HB_3]: `(${PTKP.HB_3}) - 67.500.000`,
};
export const PTKP_INPUT = {
[PTKP.TK_0]: '54000000',
[PTKP.TK_1]: '58500000',
[PTKP.TK_2]: '63000000',
[PTKP.TK_3]: '67500000',
[PTKP.K_0]: '58500000',
[PTKP.K_1]: '63000000',
[PTKP.K_2]: '67500000',
[PTKP.K_3]: '72000000',
};
export const MIN_THN_PAJAK = 2022;
export const PTKP_TITLE = {
TK: 'TK',
K: 'K',
HB: 'HB',
};
export const JENIS_PERHITUNGAN = {
HARIAN: '0',
BULANAN: '1',
};
export const JENIS_PERHITUNGAN_TEXT = {
[JENIS_PERHITUNGAN.BULANAN]: `${KODE_OBJEK_PAJAK.TIDAK_FINAL_03} : Upah (Pegawai Tidak Tetap Bulanan)`,
[JENIS_PERHITUNGAN.HARIAN]: `${KODE_OBJEK_PAJAK.TIDAK_FINAL_03} : Upah (Pegawai Tidak Tetap Non Bulanan)`,
};
export const PERHITUNGAN_BUPOT21 = {
BULANAN_GROSS: 'bulanan',
BULANAN_GROSS_UP: 'bulanan-gross-up',
BULANAN_MIXED: 'bulanan-mixed',
HARIAN: 'harian',
PASAL17: 'pasal17',
FINAL: 'final',
TIDAK_FINAL: 'final',
BULANAN: 'TARIF TER BULANAN',
PENSIUN: 'TARIF PENSIUN',
HARIAN_TER: 'TARIF TER HARIAN',
PASAL_17: 'TARIF PASAL 17',
PESANGON: 'TARIF PESANGON',
};
export const SAVE_BUPOT21 = {
BULANAN: 'bulanan',
FINAL_NON_FINAL: 'final-non-final',
};
export const FG_BUPOT = {
HARIAN: '0',
BULANAN: '1',
};
// export const FG_STATUS = {
// NORMAL: '0',
// NORMAL_PENGGANTI: '1',
// DIGANTI: '2',
// BATAL: '3',
// HAPUS: '4',
// SUBMITTED: '5',
// DRAFT: '6',
// FAILED: '7',
// PENDING: '8',
// EXTERNAL: '9',
// ON_SCHEDULE: '10',
// };
export const FG_STATUS: Record<string, string> = {
'0': 'NORMAL',
'1': 'NORMAL_PENGGANTI',
'2': 'DIGANTI',
'3': 'BATAL',
'4': 'HAPUS',
'5': 'SUBMITTED',
'6': 'DRAFT',
'7': 'FAILED',
'8': 'PENDING',
'9': 'EXTERNAL',
'10': 'ON_SCHEDULE',
};
export const FG_PDF_STATUS = {
TERBENTUK: '0',
BELUM_TERBENTUK: '1',
TIDAK_TERSEDIA: '2',
};
export const FG_PDF_STATUS_TEXT = {
[FG_PDF_STATUS.TERBENTUK]: 'Terbentuk',
[FG_PDF_STATUS.BELUM_TERBENTUK]: 'Belum Terbentuk',
[FG_PDF_STATUS.TIDAK_TERSEDIA]: 'Tidak Tersedia',
};
export const FG_SIGN_STATUS = {
IN_PROGRESS: '1',
FAILED: '2',
SIGNED: '0',
ERROR: '3',
DUPLICATE: '4',
NOT_MATCH_IDBUPOT: '5',
NOT_MATCH_STATUS: '6',
NOT_MATCH_NILAI: '7',
};
export const FG_SIGN_STATUS_TEXT = {
[FG_SIGN_STATUS.IN_PROGRESS]: 'In Progress',
[FG_SIGN_STATUS.FAILED]: 'Failed',
[FG_SIGN_STATUS.ERROR]: 'Error',
[FG_SIGN_STATUS.DUPLICATE]: 'Duplicate',
[FG_SIGN_STATUS.NOT_MATCH_IDBUPOT]: 'Not Match Id Bupot',
[FG_SIGN_STATUS.NOT_MATCH_STATUS]: 'Not Match Status',
[FG_SIGN_STATUS.NOT_MATCH_NILAI]: 'Not Match Nilai',
[FG_SIGN_STATUS.SIGNED]: 'Approved',
};
export const FG_SIGN_STATUS_COLOR = {
[FG_SIGN_STATUS.IN_PROGRESS]: 'primary',
[FG_SIGN_STATUS.FAILED]: 'error',
[FG_SIGN_STATUS.ERROR]: 'error',
[FG_SIGN_STATUS.DUPLICATE]: 'error',
[FG_SIGN_STATUS.NOT_MATCH_IDBUPOT]: 'error',
[FG_SIGN_STATUS.NOT_MATCH_STATUS]: 'error',
[FG_SIGN_STATUS.NOT_MATCH_NILAI]: 'error',
[FG_SIGN_STATUS.SIGNED]: 'success',
};
export const PENGURUS_KUASA = {
PENGURUS: '0',
KUASA: '1',
};
export const FG_STATUS_TEXT = {
[FG_STATUS.NORMAL]: 'Normal',
[FG_STATUS.NORMAL_PENGGANTI]: 'Normal Pengganti',
[FG_STATUS.SUBMITTED]: 'Submitted',
[FG_STATUS.DRAFT]: 'Draft',
[FG_STATUS.DIGANTI]: 'Diganti',
[FG_STATUS.BATAL]: 'Dibatalkan',
[FG_STATUS.HAPUS]: 'Dihapus',
[FG_STATUS.PENDING]: 'Pending',
[FG_STATUS.FAILED]: 'Failed',
[FG_STATUS.EXTERNAL]: 'External',
[FG_STATUS.ON_SCHEDULE]: 'On Schedule',
};
export const FG_STATUS_COLOR = {
[FG_STATUS.NORMAL]: 'primary',
[FG_STATUS.NORMAL_PENGGANTI]: 'primary',
[FG_STATUS.SUBMITTED]: 'primary',
[FG_STATUS.DRAFT]: 'default',
[FG_STATUS.DIGANTI]: 'secondary',
[FG_STATUS.FAILED]: 'error',
[FG_STATUS.BATAL]: 'error',
[FG_STATUS.HAPUS]: 'error',
[FG_STATUS.PENDING]: 'error',
[FG_STATUS.EXTERNAL]: 'default',
[FG_STATUS.ON_SCHEDULE]: 'warning',
};
export const FG_POSTING = {
BELUM_POSTING: '0',
SUDAH_POSTING: '1',
};
export const FG_POSTING_TEXT = {
[FG_POSTING.BELUM_POSTING]: 'Belum Posting',
[FG_POSTING.SUDAH_POSTING]: 'Sudah Posting',
};
export const FG_POSTING_COLOR = {
[FG_POSTING.BELUM_POSTING]: 'default',
[FG_POSTING.SUDAH_POSTING]: 'success',
};
export const FG_LAPOR = {
BELUM_LAPOR: '0',
SUDAH_LAPOR: '1',
};
export const FG_LAPOR_TEXT = {
[FG_LAPOR.BELUM_LAPOR]: 'Belum Lapor',
[FG_LAPOR.SUDAH_LAPOR]: 'Sudah Lapor',
};
export const FG_LAPOR_COLOR = {
[FG_LAPOR.BELUM_LAPOR]: 'error',
[FG_LAPOR.SUDAH_LAPOR]: 'success',
};
export const KD_BUPOT = {
LAINNYA: '0',
BULANAN: '1',
};
export const JNS_TRX = {
CREATE_AND_EDIT: 'NEW',
PEMBETULAN: 'EDIT',
};
export const FEATURE = {
BULANAN: '1',
TIDAK_FINAL: '2',
FINAL: '3',
A1_1721: '4',
BUPOT_26: '5',
A2_1721: '6',
REKON_BULANAN: '7',
REKON_TIDAK_FINAL: '8',
REKON_FINAL: '9',
REKON_A1_1721: '10',
REKON_BUPOT_26: '11',
MASTER_PEGAWAI_A1: '12',
MASTER_PENERIMA_PENGHASILAN: '13',
};
export const FEATURE_UPDATE_VSWP = {
BULANAN: 'a0',
FINAL_TIDAK_FINAL: '21',
A1_1721: 'a1',
UNIFIKASI: 'bpu',
PREPOP_UNIFIKASI: 'bpup',
MASTER_PEGAWAI_A1: 'masterPegawai',
MASTER_PENERIMA_PENGHASILAN: 'masterNonPegawai',
};
export const REV_NO_DEFAULT = '0';
export const FG_AKUMULASI_JUMLAH_BRUTO = {
YES: '1',
NO: '0',
};
export const DAFTAR_JNS_PEGAWAI = {
PENERIMA_PENGHASILAN: '0',
PEGAWAI_TETAP: '1',
};
export const DAFTAR_JNS_PEGAWAI_TEXT = {
[DAFTAR_JNS_PEGAWAI.PENERIMA_PENGHASILAN]: 'Daftar Selain Pegawai Tetap',
[DAFTAR_JNS_PEGAWAI.PEGAWAI_TETAP]: 'Daftar Pegawai Tetap',
};
export const FG_JENIS_BUKTI_POTONG_NON_PLUS = {
BULANAN_TIDAK_FINAL: '0',
TIDAK_FINAL_FINAL: '1',
TAHUNAN: '2',
BUPOT_26: '3',
};
export const FG_JENIS_BUKTI_POTONG = {
BULANAN: '0',
TIDAK_FINAL_FINAL: '1',
TAHUNAN: '2',
BUPOT_26: '3',
REKON_BULANAN: '4',
REKON_FINAL_TDK_FINAL: '5',
};
export const FG_JENIS_BUKTI_POTONG_TEXT = {
[FG_JENIS_BUKTI_POTONG.BULANAN]: 'Bulanan',
[FG_JENIS_BUKTI_POTONG.TIDAK_FINAL_FINAL]: 'Final/Tidak Final',
[FG_JENIS_BUKTI_POTONG.TAHUNAN]: 'Tahunan',
[FG_JENIS_BUKTI_POTONG.BUPOT_26]: 'Bupot 26',
[FG_JENIS_BUKTI_POTONG.REKON_BULANAN]: 'Rekon Bupot Bulanan (Genta)',
[FG_JENIS_BUKTI_POTONG.REKON_FINAL_TDK_FINAL]: 'Rekon Bupot Final/Tidak Final (Genta)',
};
export const FG_JENIS_BUKTI_POTONG_NON_PLUS_TEXT = {
[FG_JENIS_BUKTI_POTONG_NON_PLUS.BULANAN_TIDAK_FINAL]: 'Bulanan',
[FG_JENIS_BUKTI_POTONG_NON_PLUS.TIDAK_FINAL_FINAL]: 'Final/Tidak Final',
[FG_JENIS_BUKTI_POTONG_NON_PLUS.TAHUNAN]: 'Tahunan',
[FG_JENIS_BUKTI_POTONG_NON_PLUS.BUPOT_26]: 'Bupot 26',
};
export const FG_JENIS_BUKTI_POTONG_PLUS = {
BULANAN: '0',
TIDAK_FINAL_FINAL: '1',
BUPOT_26: '3',
};
export const FG_JENIS_BUKTI_POTONG_PLUS_TEXT = {
[FG_JENIS_BUKTI_POTONG_PLUS.BULANAN]: 'Bulanan',
[FG_JENIS_BUKTI_POTONG_PLUS.TIDAK_FINAL_FINAL]: 'Tidak Final/Final',
[FG_JENIS_BUKTI_POTONG_PLUS.BUPOT_26]: 'Bupot 26',
};
export const FG_TAHUNAN = {
YES: '1',
NO: '0',
};
export const FG_TAHUNAN_TEXT = {
[FG_TAHUNAN.YES]: 'Ya',
[FG_TAHUNAN.NO]: 'Tidak',
};
export const FG_DATA_IMPORT = {
BARU: '0',
UPDATE: '1',
};
export const FG_DATA_IMPORT_TEXT = {
[FG_DATA_IMPORT.BARU]: 'Data Baru',
[FG_DATA_IMPORT.UPDATE]: 'Data Detail Eksternal',
};
export const FG_TRANSACTION = {
CREATE_OR_EDIT: 'NEW',
PENGGANTI: 'EDIT',
CANCEL: 'CANCELED',
};
export const JENIS_REFERENSI = {
PENERIMA_PENGHASILAN: '0',
PEGAWAI_TETAP: '1',
};
export const FG_FASILITAS_PPH_21 = {
DTP: '4',
FASILITAS_LAINNYA: '8',
TANPA_FASILITAS: '9',
SKB_PPH_PASAL_21: '10',
DTP_PPH_PASAL_21: '11',
};
export const FG_FASILITAS_PPH_21_TEXT = {
[FG_FASILITAS_PPH_21.DTP]: 'DTP',
[FG_FASILITAS_PPH_21.TANPA_FASILITAS]: 'Tanpa Fasilitas',
[FG_FASILITAS_PPH_21.FASILITAS_LAINNYA]: 'Fasilitas Lainnya',
[FG_FASILITAS_PPH_21.SKB_PPH_PASAL_21]: 'SKB PPh Pasal 21',
};
export const STATUS_PPH = {
FINAL: 'Final',
TIDAK_FINAL: 'Tidak Final',
};
export const PASAL_PPH = {
PASAL_21: 'Pasal 21',
};
export const BUPOT_REFERENSI = {
PENGUMUMAN: 'ANNOUNCEMENT',
INVOICE: 'COMMERCIALINVOICE',
KONTRAK: 'CONTRACT',
NOMOR_AKUN: 'CURRENTACCOUNT',
AKTA_PERJANJIAN: 'DEEDOFENGAGEMENT',
AKTA_RUPS: 'DEEDOFGENERAL',
LAINNYA: 'OTHER',
BUKTI_BAYAR: 'PAYMENTPROOF',
SURAT_PERNYATAAN: 'STATEMENTLETTER',
FAKTUR_PAJAK: 'TAXINVOICE',
KONFIRMASI_TRANSAKSI: 'TRADECONFIRMATION',
};
export const BUPOT_REFERENSI_TEXT = {
[BUPOT_REFERENSI.PENGUMUMAN]: 'Pengumuman',
[BUPOT_REFERENSI.INVOICE]: 'Faktur pembelian',
[BUPOT_REFERENSI.KONTRAK]: 'Kontrak',
[BUPOT_REFERENSI.NOMOR_AKUN]: 'Nomor account',
[BUPOT_REFERENSI.AKTA_PERJANJIAN]: 'Akta perjanjian',
[BUPOT_REFERENSI.AKTA_RUPS]: 'Akta RUPS',
[BUPOT_REFERENSI.LAINNYA]: 'Lainnya',
[BUPOT_REFERENSI.BUKTI_BAYAR]: 'Bukti bayar',
[BUPOT_REFERENSI.SURAT_PERNYATAAN]: 'Surat pernyataan',
[BUPOT_REFERENSI.FAKTUR_PAJAK]: 'Faktur Pajak',
[BUPOT_REFERENSI.KONFIRMASI_TRANSAKSI]: 'Konfirmasi transaksi',
};
export const PANGKAT_GOLONGAN = {
GOLONGAN_I: 'Golongan I',
GOLONGAN_II: 'Golongan II',
GOLONGAN_III: 'Golongan III',
GOLONGAN_IV: 'Golongan IV',
};
export const JENIS_REKAP = {
DETAIL: 'detail',
SUMMARY: 'summary',
};
// Start Dari Sini
export const FG_FASILITAS_DN = {
SKB_PPH_PASAL_22: '1',
SKB_PPH_PASAL_23: '2',
SKB_PPH_PHTB: '3',
DTP: '4',
SKB_PPH_BUNGA_DEPOSITO_DANA_PENSIUN_TABUNGAN: '5',
SUKET_PP23_PP52: '6',
SKD_WPLN: '7',
FASILITAS_LAINNYA: '8',
TANPA_FASILITAS: '9',
SKB_PPH_PASAL_21: '10',
DTP_PPH_PASAL_21: '11',
};
export const FG_FASILITAS_OPTIONS = {
SKB_PPH_PASAL_22: '1',
SKB_PPH_PASAL_23: '2',
SKB_PPH_PHTB: '3',
DTP: '4',
SKB_PPH_BUNGA_DEPOSITO_DANA_PENSIUN_TABUNGAN: '5',
SUKET_PP23_PP52: '6',
SKD_WPLN: '7',
FASILITAS_LAINNYA: '8',
TANPA_FASILITAS: '9',
SKB_PPH_PASAL_21: '10',
DTP_PPH_PASAL_21: '11',
};
export const FG_FASILITAS_MASTER_KEY = {
[FG_FASILITAS_DN.SKB_PPH_PASAL_21]: 'skb21',
[FG_FASILITAS_DN.SKB_PPH_PASAL_22]: 'skbPasal22',
[FG_FASILITAS_DN.SKB_PPH_PASAL_23]: 'skbPasal23',
[FG_FASILITAS_DN.SKB_PPH_PHTB]: 'skbPHTB',
[FG_FASILITAS_DN.SKD_WPLN]: 'certofDomicile',
[FG_FASILITAS_DN.DTP]: 'dtp',
[FG_FASILITAS_DN.SKB_PPH_BUNGA_DEPOSITO_DANA_PENSIUN_TABUNGAN]: 'skbBungaTabungan',
[FG_FASILITAS_DN.SUKET_PP23_PP52]: 'suket',
[FG_FASILITAS_DN.FASILITAS_LAINNYA]: 'otherCert',
[FG_FASILITAS_DN.TANPA_FASILITAS]: 'noCertificate',
};
export const FG_FASILITAS_TEXT = {
[FG_FASILITAS_DN.SKB_PPH_PASAL_22]: 'SKB PPh Pasal 22',
[FG_FASILITAS_DN.SKB_PPH_PASAL_23]: 'SKB PPh Pasal 23',
[FG_FASILITAS_DN.SKB_PPH_PHTB]: 'SKB PPh PHTB',
[FG_FASILITAS_DN.DTP]: 'DTP',
[FG_FASILITAS_DN.SKB_PPH_BUNGA_DEPOSITO_DANA_PENSIUN_TABUNGAN]:
'SKB PPh atas bunga deposito, dana pensiun, tabungan',
[FG_FASILITAS_DN.SKD_WPLN]: 'SKD WPLN',
[FG_FASILITAS_DN.SUKET_PP23_PP52]: 'Suket PP23/PP52',
[FG_FASILITAS_DN.FASILITAS_LAINNYA]: 'Fasilitas Lainnya',
[FG_FASILITAS_DN.TANPA_FASILITAS]: 'Tanpa Fasilitas',
[FG_FASILITAS_DN.SKB_PPH_PASAL_21]: 'SKB PPh Pasal 21',
[FG_FASILITAS_DN.DTP_PPH_PASAL_21]: 'DTP PPh Pasal 21',
};
export const TARIF_0 = [
FG_FASILITAS_DN.SKB_PPH_PASAL_22,
FG_FASILITAS_DN.SKB_PPH_PASAL_23,
FG_FASILITAS_DN.SKB_PPH_PHTB,
FG_FASILITAS_DN.SKB_PPH_BUNGA_DEPOSITO_DANA_PENSIUN_TABUNGAN,
];
export const JENIS_DOKUMEN = [
{
value: 'ANNOUNCEMENT',
label: 'Pengumuman',
},
{
value: 'COMMERCIALINVOICE',
label: 'Faktur pembelian',
},
{
value: 'CONTRACT',
label: 'Kontrak',
},
{
value: 'CURRENTACCOUNT',
label: 'Nomor account',
},
{
value: 'DEEDOFENGAGEMENT',
label: 'Akta perjanjian',
},
{
value: 'DEEDOFGENERAL',
label: 'Akta RUPS',
},
{
value: 'PAYMENTPROOF',
label: 'Bukti bayar',
},
{
value: 'STATEMENTLETTER',
label: 'Surat pernyataan',
},
{
value: 'TAXINVOICE',
label: 'Faktur Pajak',
},
{
value: 'TRADECONFIRMATION',
label: 'Konfirmasi transaksi',
},
{
value: 'OTHER',
label: 'Lainnya',
},
];
export const PANDUAN_REKAM_DN = {
description: {
intro:
'Form ini digunakan untuk melakukan perekaman data baru Bukti Pemotongan PPh Unifikasi serta perubahannya.\n',
textList: 'Form ini terdiri dari beberapa bagian antara lain:',
list: [
'Bagian I, Identitas Wajib Pajak yang Dipotong/Dipungut',
'Bagian II, Pajak Penghasilan yang Dipotong/Dipungut',
'Bagian III, Dokumen Dasar Pemotongan',
'Bagian IV, Identitas Pemotong Pajak',
],
closing: 'Berikut ini petunjuk pengisian pada masing-masing bagian:',
},
sections: [
{
title: 'BAGIAN I, Identitas Wajib Pajak yang Dipotong/Dipungut',
items: [
{
text: 'Tahun Pajak, Tentukan tahun pajak saat melakukan pemotongan Pajak Penghasilan, tahun paling awal adalah 2022.',
subItems: [],
},
{
text: 'Masa Pajak, Tentukan Masa Pajak yang sesuai untuk transaksi pemotongan Pajak Penghasilan, pilihan terdiri dari masa Januari s.d. Desember bergantung pada tahun pajak yang Anda pilih.',
subItems: [],
},
{
text: 'Identitas, isikan sesuai dengan nomor identitas, identitas yang diperbolehkan digunakan hanya NPWP atau NIK (KTP), jika tidak memiliki, maka tidak diperbolehkan untuk dilakukan perekaman data dengan ketentuan yang berlaku.',
subItems: [],
},
{
text: 'Tidak diperbolehkan menggunakan identitas yang tidak valid.',
subItems: [],
},
{
text: 'Nama, isikan dengan nama Wajib Pajak sesuai dengan nomor identitas yang dimasukkan.',
subItems: [],
},
{
text: 'Jika terdapat transaksi perwakilan, centang kolom QQ dan isikan nama Wajib Pajak yang diwakili.',
subItems: [],
},
{
text: 'Jika menggunakan NIK, isikan kolom Alasan pengisian NIK tanpa validasi.',
subItems: [],
},
],
},
{
title: 'BAGIAN II, Penghasilan yang Dipotong/Dipungut',
items: [
{
text: 'Pilih Kode Objek Pajak dari pilihan yang tersedia, Anda dapat mengetikkan kata kunci untuk mempercepat pencarian objek pajak.',
subItems: [],
},
{
text: 'Pilihlah fasilitas yang dimiliki oleh Wajib Pajak:',
subItems: [
'Jika tidak memiliki fasilitas, maka pilihlah pilihan tanpa fasilitas pada pilihan yang tersedia. Tidak memiliki fasilitas akan menjadi pilihan utama (default).',
'Jika memiliki fasilitas berupa Surat Keterangan Bebas (SKB), masukkan nomor SKB yang dimiliki.',
'Jika memiliki fasilitas DTP, masukkan Nomor Dokumen Pendukung.',
'Jika memiliki fasilitas lainnya, masukkan Nomor Dokumen Fasilitas lainnya.',
],
},
{
text: 'Dengan memilih Kode Objek Pajak, Sistem akan melakukan pencarian secara otomatis tarif dari jenis objek pajak.',
subItems: [],
},
{
text: 'Isikan nilai nominal Penghasilan Bruto pada kotak yang tersedia, Sistem akan menghitung secara otomatis nilai Pajak Penghasilan yang Dipotong/Dipungut.',
subItems: [],
},
],
},
{
title: 'BAGIAN III, Dokumen Pendukung',
items: [
{
text: 'Anda diharuskan mengisi minimal 1 (satu) dokumen pendukung untuk pemotongan penghasilan. Untuk mengisi dokumen pendukung, klik tombol tambah, kemudian, isilah data dokumen pendukung yang sesuai.',
subItems: [],
},
],
},
{
title: 'BAGIAN IV, Identitas Pemotong Pajak',
items: [
{
text: 'Pastikan Anda telah melakukan perekaman data penandatangan pada menu Pengaturan, sebelum melakukan perekaman bukti pemotongan.',
subItems: [],
},
{
text: 'Pada bagian ini, Anda harus menentukan, pihak yang akan menandatangani dokumen bukti pemotongan ini apakah Wakil Wajib Pajak/Pengurus atau Kuasa.',
subItems: [],
},
{
text: 'Pastikan isian Anda telah lengkap dan benar, kemudian centang pernyataan yang disediakan yang menunjukkan Anda telah dengan seksama memastikan kebenaran isi dari bukti pemotongan yang dibuat, kemudian klik tombol simpan untuk menyimpan data.',
subItems: [],
},
],
},
],
};
export const ActionRekam = {
REKAM: 'rekam',
PENGGANTI: 'pengganti',
UBAH: 'ubah',
RETUR: 'retur',
PEMBETULAN: 'pembetulan',
};
const appRootKey = 'unifikasi';
const queryKey = {
getKodeObjekPajak: (params: any) => [appRootKey, 'kode-objek-pajak', params],
bulanan: {
all: (params: any) => [appRootKey, 'bulanan', params],
detail: (params: any) => [appRootKey, 'bulanan', 'detail', params],
draft: [appRootKey, 'bulanan', 'draft'],
delete: [appRootKey, 'bulanan', 'delete'],
upload: [appRootKey, 'bulanan', 'upload'],
cancel: [appRootKey, 'bulanan', 'cancel'],
cetakPdf: (params: any) => [appRootKey, 'bulanan-cetak-pdf', params],
},
};
export default queryKey;
// /* eslint-disable no-lonely-if */
// import {
// escapeForPostgres,
// filterModelByOperator,
// transformModelOperatorToSqlOperator,
// } from '@pjap/shared/data-grid-premium/util';
// import { isArray } from 'lodash';
// /**
// * Configuration object for customizing how individual fields are processed during search
// *
// * @typedef {Object} FieldConfig
// * @property {string} [type] - Data type of the field ('string', 'date', 'number', etc.)
// * Used for applying appropriate transformations and SQL operations
// *
// * @property {function} [transformValue] - Custom function to transform field values before SQL generation
// * Signature: (value, modelOperator) => transformedValue
// * Example: Convert date formats, escape special characters
// *
// * @property {function} [transformField] - Custom function to transform field names
// * Signature: (fieldName, value) => transformedFieldName
// * Example: Map UI field names to database column names
// *
// * @property {string|string[]} [fieldMapping] - Direct mapping to different database field name(s)
// * String: Maps to single field
// * Array: Maps to multiple fields (OR condition)
// *
// * @property {function|string} [operator] - Custom operator transformation
// * Function: (sqlOperator, originalOperator, value) => newOperator
// * String: Fixed operator to use (e.g., 'LIKE', 'IN')
// *
// * @property {function} [additionalQuery] - Generates additional SQL conditions for complex searches
// * Signature: (value) => additionalSqlCondition
// * Example: Add related table joins or complex WHERE clauses
// *
// * @property {string} [additionalQueryLogicOperator='AND'] - Logic operator ('AND' or 'OR') to combine
// * main query with additional query
// */
// /**
// * Configuration options for the useAdvancedSearch hook
// *
// * @typedef {Object} UseAdvancedSearchOptions
// * @property {Object.<string, FieldConfig>} [fieldConfigs] - Field-specific configurations
// * Key: field name, Value: FieldConfig object
// *
// * @property {function} [globalValueTransform] - Global transformation applied to all field values
// * Signature: (fieldName, value) => transformedValue
// * Applied when no field-specific transform exists
// *
// * @property {function} [globalFieldTransform] - Global transformation applied to all field names
// * Signature: (fieldName) => transformedFieldName
// * Applied when no field-specific transform exists
// *
// * @property {Object.<string, string>} [defaultFieldTypes] - Default data types for fields
// * Key: field name, Value: type string
// * Used when field type not specified in fieldConfigs
// */
// /**
// * Advanced Search Hook
// *
// * Provides functionality to convert DataGrid Premium filter models into SQL WHERE clauses
// * with extensive customization options for field transformations, value processing, and
// * complex query generation.
// *
// * MAIN USE CASES:
// * 1. Convert frontend filter UI state to backend SQL queries
// * 2. Handle complex field mappings (UI field → database column)
// * 3. Apply custom value transformations (dates, case sensitivity, escaping)
// * 4. Generate complex queries with joins and additional conditions
// *
// * @param {UseAdvancedSearchOptions} options - Configuration options for customizing search behavior
// * @returns {Object} Object containing the main query generation function
// */
// const useAdvancedSearch = (options = {}) => {
// // Extract configuration options with defaults
// const {
// fieldConfigs = {}, // Field-specific configurations
// globalValueTransform, // Global value transformation function
// globalFieldTransform, // Global field name transformation function
// defaultFieldTypes = {}, // Default field type mappings
// } = options;
// /**
// * Transform Field Values
// *
// * Applies transformations to field values based on priority:
// * 1. Field-specific transformValue function (highest priority)
// * 2. Global value transformation function
// * 3. Built-in type-based transformations (lowest priority)
// *
// * BUILT-IN TRANSFORMATIONS:
// * - 'date': Removes hyphens and underscores (2023-01-01 → 20230101)
// * - 'string': Converts to lowercase and escapes for PostgreSQL
// * - default: Returns value unchanged
// *
// * @param {string} field - The field name being processed
// * @param {any} value - The original field value from the filter
// * @param {string} modelOperator - The operator from the DataGrid model
// * @returns {any} The transformed value ready for SQL generation
// */
// const transformValue = (field, value = '', modelOperator) => {
// // Skip transformation for null/undefined values
// // if (!value) return value;
// // Priority 1: Field-specific transformation
// if (fieldConfigs[field]?.transformValue) {
// return fieldConfigs[field].transformValue(value, modelOperator);
// }
// // Priority 2: Global transformation
// if (globalValueTransform) {
// return globalValueTransform(field, value);
// }
// // Priority 3: Built-in type-based transformations
// const fieldType = fieldConfigs[field]?.type || defaultFieldTypes[field] || 'string';
// switch (fieldType) {
// case 'date':
// // Remove date separators for database storage format
// return value.replace(/-/g, '').replace(/_/g, '');
// case 'string':
// // Convert to lowercase and escape special PostgreSQL characters
// return escapeForPostgres(typeof value === 'string' ? value.toLowerCase() : value);
// default:
// // No transformation for other types (numbers, booleans, etc.)
// return value;
// }
// };
// /**
// * Transform Field Names
// *
// * Maps UI field names to database column names with priority:
// * 1. Direct fieldMapping configuration (highest priority)
// * 2. Field-specific transformField function
// * 3. Global field transformation function
// * 4. Original field name (lowest priority)
// *
// * FIELD MAPPING EXAMPLES:
// * - String mapping: "userName" → "user_name"
// * - Array mapping: "fullName" → ["first_name", "last_name"] (creates OR condition)
// *
// * @param {string} field - The original field name from the UI
// * @param {any} value - The field value (may influence transformation)
// * @returns {string|string[]} The transformed field name(s) for database queries
// */
// const transformField = (field, value) => {
// // Priority 1: Direct field mapping
// if (fieldConfigs[field]?.fieldMapping) {
// return fieldConfigs[field].fieldMapping;
// }
// // Priority 2: Field-specific transformation function
// if (fieldConfigs[field]?.transformField) {
// return fieldConfigs[field].transformField(field, value);
// }
// // Priority 3: Global field transformation
// if (globalFieldTransform) {
// return globalFieldTransform(field);
// }
// // Priority 4: Use original field name
// return field;
// };
// /**
// * Transform SQL Operators
// *
// * Converts DataGrid operators to appropriate SQL operators with custom logic support.
// * Allows field-specific operator overrides for special cases.
// *
// * OPERATOR EXAMPLES:
// * - DataGrid "contains" → SQL "LIKE"
// * - DataGrid "equals" → SQL "="
// * - Custom: Force "ILIKE" for case-insensitive searches
// *
// * @param {string} field - The field name being processed
// * @param {string} operator - The original operator from DataGrid
// * @param {any} value - The field value (may influence operator choice)
// * @returns {string} The SQL operator to use in the query
// */
// const transformOperator = (field, operator, value) => {
// // Check for field-specific operator configuration
// if (fieldConfigs[field]?.operator) {
// // If operator config is a function, call it with context
// if (typeof fieldConfigs[field].operator === 'function') {
// console.log('ini kesini lagi yah');
// return fieldConfigs[field].operator(
// transformModelOperatorToSqlOperator(operator), // Converted SQL operator
// operator, // Original DataGrid operator
// value // Field value for context
// );
// }
// console.log('ini keskip berarti');
// // If operator config is a string, use it directly
// return fieldConfigs[field].operator;
// }
// // Use default operator transformation
// return operator;
// };
// const buildQuery = (item, transformedField, fieldValue) => {
// // Transform the field value with all configured transformations
// const value = transformValue(item.columnField, fieldValue, item.operatorValue);
// // Transform the operator for this field
// const operator = transformOperator(item.columnField, item.operatorValue, fieldValue);
// console.log('ini operator datagrid', operator);
// // Get the field type for proper SQL generation
// const fieldType =
// fieldConfigs[item.columnField]?.type || defaultFieldTypes[item.columnField] || 'string';
// let baseQuery;
// // Handle multiple field mappings (when field is an array)
// if (Array.isArray(transformedField)) {
// // Check if we have multiple values for multiple fields
// if (typeof value === 'object') {
// // Map each field to its corresponding value (parallel arrays)
// // Example: fields=["first_name", "last_name"], values={0: "John", 1: "Doe"}
// // Result: first_name = 'John' AND last_name = 'Doe'
// baseQuery = transformedField
// .map((f, index) => {
// const newValue = Object.values(value)[index];
// return filterModelByOperator(f, operator, newValue, fieldType);
// })
// .join(' AND ');
// } else {
// // Same value applied to all fields (OR condition)
// // Example: fields=["first_name", "last_name"], value="John"
// // Result: first_name LIKE '%John%' OR last_name LIKE '%John%'
// baseQuery = transformedField
// .map((f) => filterModelByOperator(f, operator, value, fieldType))
// .join(' OR ');
// }
// } else {
// // Single field mapping
// // Handle custom operators that need special SQL construction
// if (fieldConfigs[item.columnField]?.operator) {
// baseQuery = `LOWER("${transformedField}") ${operator} ${value}`;
// } else {
// baseQuery = filterModelByOperator(transformedField, operator, value, fieldType);
// }
// }
// // Handle additional query conditions (for complex search scenarios)
// if (fieldConfigs[item.columnField]?.additionalQuery) {
// // Generate additional SQL condition
// const additionalQuery = fieldConfigs[item.columnField].additionalQuery(fieldValue);
// // Get the logic operator for combining base and additional queries
// const logicOperator = fieldConfigs[item.columnField]?.additionalQueryLogicOperator || 'AND';
// // Combine base and additional queries if additional query exists
// if (additionalQuery) {
// return `(${[baseQuery, additionalQuery].filter(Boolean).join(` ${logicOperator} `)})`;
// }
// }
// return baseQuery;
// };
// /**
// * Generate SQL Query from DataGrid Filter Model
// *
// * Main function that converts a DataGrid Premium filter model into a SQL WHERE clause.
// * Handles complex scenarios including:
// * - Multiple field mappings (OR conditions)
// * - Custom operators and value transformations
// * - Additional query conditions with configurable logic operators
// * - Proper SQL escaping and type handling
// *
// * FILTER MODEL STRUCTURE:
// * {
// * items: [
// * {
// * columnField: "fieldName",
// * operatorValue: "contains",
// * value: "searchTerm"
// * }
// * ],
// * linkOperator: "AND" // or "OR"
// * }
// *
// * @param {Object} model - The filter model from DataGrid Premium
// * @param {Array} model.items - Array of individual filter conditions
// * @param {string} model.linkOperator - Logic operator to combine multiple conditions ("AND" or "OR")
// * @returns {string} Generated SQL WHERE clause (without the "WHERE" keyword)
// */
// const generateSqlQueryByDatagridPremiumModel = (model) => {
// // Process each filter item in the model
// const advanced = model.items
// ?.map((item) => {
// // Transform the field name (UI → database column)
// const field = transformField(item.columnField, item.value);
// if (item.operatorValue === 'isAnyOf' && isArray(item.value)) {
// if (item.value.length > 0) {
// const query = item.value.map((v) => `${buildQuery(item, field, v)}`).join(' OR ');
// return `(${query})`;
// }
// return '';
// }
// return buildQuery(item, field, item.value);
// })
// // Remove any empty/null queries
// .filter(Boolean)
// // Join all conditions with the model's link operator (AND/OR)
// .join(` ${model.linkOperator} `);
// return advanced;
// };
// // Return the main query generation function
// return {
// generateSqlQueryByDatagridPremiumModel,
// };
// };
// export default useAdvancedSearch;
// /* eslint-disable consistent-return */
// /* eslint-disable no-useless-return */
// import useAdvancedSearch from '@pjap/shared/hooks/useAdvancedSearch';
// import {
// transformComparisonOperatorToPatternMatchingOperator,
// transformOperatorToPatternMatchingOrContainsOperatorByModelOperator,
// transformValueToQueryValueBasedOnModelOperator,
// } from '@pjap/shared/data-grid-premium/util';
// import {
// FG_PDF_STATUS,
// FG_SIGN_STATUS,
// FG_STATUS,
// } from '@pjap/pph21/app/bukti-potong/shared/constants/BupotConstant';
// import { addLeadingZero } from '@pjap/shared/utils/number';
// import { transformDateToCommonFormat } from '@pjap/pph21/utils/formatDate';
// const useBpuAdvancedSearch = () => {
// const fieldConfigs = {
// fgStatus: {
// fieldMapping: 'fgStatus',
// operator: transformComparisonOperatorToPatternMatchingOperator,
// transformValue: (value) => {
// switch (value) {
// case FG_STATUS.SUBMITTED:
// return "'%SUBMITTED%'".toLowerCase();
// case FG_STATUS.NORMAL:
// return "'%NORMAL%'".toLowerCase();
// case FG_STATUS.NORMAL_PENGGANTI:
// return "'%AMENDMENT%'".toLowerCase();
// case FG_STATUS.BATAL:
// return "'%CANCELLED%'".toLowerCase();
// case FG_STATUS.DRAFT:
// return "'%DRAFT%'".toLowerCase();
// case FG_STATUS.DIGANTI:
// return "'%AMENDED%'".toLowerCase();
// case FG_STATUS.PENDING:
// return "'%PENDING%'".toLowerCase();
// case FG_STATUS.ON_SCHEDULE:
// return "'%ON SCHEDULE%'".toLowerCase();
// default:
// return `'%${value}%'`.toLowerCase();
// }
// },
// },
// fgSignStatus: {
// fieldMapping: 'fgStatus',
// operator: (currentOperator, _, value) => {
// if (value === FG_SIGN_STATUS.ERROR) return 'IN';
// if (currentOperator === '!=') return 'NOT LIKE';
// return 'LIKE';
// },
// transformValue: (value) => {
// switch (value) {
// case FG_SIGN_STATUS.IN_PROGRESS:
// return "'%SIGNING_IN_PROGRESS%'".toLowerCase();
// case FG_SIGN_STATUS.FAILED:
// return "'%DJP-SIGN-MASTER%'".toLowerCase();
// case FG_SIGN_STATUS.SIGNED:
// return "'%document signed successfully%'".toLowerCase();
// case FG_SIGN_STATUS.NOT_MATCH_IDBUPOT:
// return "'%NOT_MATCH_IDBUPOT%'".toLowerCase();
// case FG_SIGN_STATUS.NOT_MATCH_STATUS:
// return "'%NOT_MATCH_STATUS%'".toLowerCase();
// case FG_SIGN_STATUS.NOT_MATCH_NILAI:
// return "'%NOT_MATCH_NILAI%'".toLowerCase();
// case FG_SIGN_STATUS.DUPLICATE:
// return "'%DUPLICATE%'".toLowerCase();
// case FG_SIGN_STATUS.ERROR:
// return "('draft','normal-done','amendment-done','amended-document signed successfully','cancelled-done','submitted-signing_in_progress')";
// default:
// return `'%${value}%'`.toLowerCase();
// }
// },
// additionalQuery: (originalValue) => {
// switch (originalValue) {
// case FG_SIGN_STATUS.ERROR:
// return `"errorMsg" IS NOT NULL AND "errorMsg" != ''`;
// default:
// return ``;
// }
// },
// },
// fgPdf: {
// fieldMapping: 'link',
// operator: transformComparisonOperatorToPatternMatchingOperator,
// transformValue: (value) => {
// switch (value) {
// case FG_PDF_STATUS.TERBENTUK:
// return "'%https://coretaxdjp.pajak.go.id%'";
// case FG_PDF_STATUS.BELUM_TERBENTUK:
// return `'%intranet.pajak.go.id%' OR ((link = '' OR link IS NULL) AND "fgStatus" IN ('NORMAL-document signed successfully', 'NORMAL-Done', 'AMENDED-document signed successfully', 'CANCELLED-document signed successfully', 'AMENDMENT-document signed successfully'))`;
// default:
// return "'' OR link IS NULL";
// }
// },
// },
// fgKirimEmail: {
// fieldMapping: 'fgkirimemail',
// },
// msPajak: {
// fieldMapping: 'masaPajak',
// transformValue: (value) => `'${addLeadingZero(value)}'`,
// },
// masaPajak: {
// fieldMapping: 'masaPajak',
// transformValue: (value) => `'${addLeadingZero(value)}'`,
// },
// thnPajak: {
// fieldMapping: 'tahunPajak',
// },
// tahunPajak: {
// fieldMapping: 'tahunPajak',
// },
// dpp: {
// fieldMapping: 'dpp',
// },
// noBupot: {
// fieldMapping: 'nomorBupot',
// transformValue: transformValueToQueryValueBasedOnModelOperator,
// operator: transformOperatorToPatternMatchingOrContainsOperatorByModelOperator,
// },
// npwp: {
// operator: transformOperatorToPatternMatchingOrContainsOperatorByModelOperator,
// transformValue: transformValueToQueryValueBasedOnModelOperator,
// },
// idTku: {
// operator: transformOperatorToPatternMatchingOrContainsOperatorByModelOperator,
// transformValue: transformValueToQueryValueBasedOnModelOperator,
// },
// errorMsg: {
// operator: transformComparisonOperatorToPatternMatchingOperator,
// transformValue: (value) => `'%${value}%'`.toLowerCase(),
// },
// jmlBruto: {
// fieldMapping: 'penghasilanBruto',
// },
// kdObjPjk: {
// fieldMapping: 'kodeObjekPajak',
// operator: transformOperatorToPatternMatchingOrContainsOperatorByModelOperator,
// transformValue: transformValueToQueryValueBasedOnModelOperator,
// },
// npwpPemotong: {
// fieldMapping: 'npwpPemotong',
// },
// namaPemotong: {
// fieldMapping: 'namaPemotong',
// },
// namaDipotong: {
// fieldMapping: 'nama',
// },
// pasalPPh: {
// fieldMapping: 'pasalPPh',
// operator: transformOperatorToPatternMatchingOrContainsOperatorByModelOperator,
// transformValue: transformValueToQueryValueBasedOnModelOperator,
// },
// tanggalApproval: {
// transformValue: transformDateToCommonFormat,
// },
// internal_id: {
// fieldMapping: 'internal_id',
// operator: transformOperatorToPatternMatchingOrContainsOperatorByModelOperator,
// transformValue: transformValueToQueryValueBasedOnModelOperator,
// },
// created: {
// fieldMapping: 'created_by',
// operator: transformOperatorToPatternMatchingOrContainsOperatorByModelOperator,
// transformValue: transformValueToQueryValueBasedOnModelOperator,
// },
// updated: {
// fieldMapping: 'updated_by',
// operator: transformOperatorToPatternMatchingOrContainsOperatorByModelOperator,
// transformValue: transformValueToQueryValueBasedOnModelOperator,
// },
// };
// const defaultFieldTypes = {
// created_at: 'date',
// updated_at: 'date',
// };
// return useAdvancedSearch({
// fieldConfigs,
// defaultFieldTypes,
// });
// };
// export default useBpuAdvancedSearch;
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,
TGetListDataTableDn,
TGetListDataTableDnResult,
} from '../types/types';
import bulananApi from '../utils/api';
export const transformFgStatusToFgSignStatus = (fgStatus: any) => {
console.log('🚀 ~ transformFgStatusToFgSignStatus ~ fgStatus:', fgStatus);
const splittedFgStatus = fgStatus?.split('-') || [];
if (splittedFgStatus.includes('SIGN') > 0) {
// failed
return FG_SIGN_STATUS.FAILED;
}
if (splittedFgStatus.includes('SIGNING IN PROGRESS')) {
return FG_SIGN_STATUS.IN_PROGRESS;
}
if (fgStatus === 'DUPLICATE') {
return FG_SIGN_STATUS.DUPLICATE;
}
if (fgStatus === 'NOT_MATCH_STATUS') {
return FG_SIGN_STATUS.NOT_MATCH_STATUS;
}
if (fgStatus === 'NOT_MATCH_NILAI') {
return FG_SIGN_STATUS.NOT_MATCH_NILAI;
}
if (fgStatus === 'NOT_MATCH_IDBUPOT') {
return FG_SIGN_STATUS.NOT_MATCH_IDBUPOT;
}
switch (splittedFgStatus[1]) {
case 'document signed successfully':
case 'Done':
return FG_SIGN_STATUS.SIGNED;
case 'SIGNING_IN_PROGRESS':
return FG_SIGN_STATUS.IN_PROGRESS;
case 'DUPLICATE':
return FG_SIGN_STATUS.DUPLICATE;
case 'NOT_MATCH_STATUS':
return FG_SIGN_STATUS.NOT_MATCH_STATUS;
case 'NOT_MATCH_IDBUPOT':
return FG_SIGN_STATUS.NOT_MATCH_IDBUPOT;
default:
return null;
}
};
export const getFgStatusPdf = (link: any, fgSignStatus: any) => {
if (isEmpty(link) || [FG_SIGN_STATUS.IN_PROGRESS].includes(fgSignStatus))
return FG_PDF_STATUS.TIDAK_TERSEDIA;
if (!link.includes('https://coretaxdjp.pajak.go.id/')) return FG_PDF_STATUS.BELUM_TERBENTUK;
return FG_PDF_STATUS.TERBENTUK;
};
export const transformSortModelToSortApiPayload = (transformedModel: any) => ({
sortingMode: transformedModel.map((item: any) => item.field).join(','),
sortingMethod: transformedModel.length > 0 ? transformedModel[0].sort : 'desc',
});
const normalisePropsGetDn = (params: TGetListDataTableDn) => ({
...params,
nomorSP2D: params.dokumen_referensi?.[0]?.nomorSP2D || '',
metodePembayaranBendahara: params.dokumen_referensi?.[0]?.metodePembayaranBendahara || '',
dokReferensi: params.dokumen_referensi?.[0]?.dokReferensi || '',
nomorDokumen: params.dokumen_referensi?.[0]?.nomorDokumen || '',
id: params.id,
npwpPemotong: params.npwpPemotong,
idBupot: params.idBupot,
internal_id: params.internal_id,
// fgStatus: FG_STATUS[params.fgStatus],
fgStatus: params.fgStatus,
fgSignStatus: transformFgStatusToFgSignStatus(params.fgStatus),
fgPdf: getFgStatusPdf(params.link, transformFgStatusToFgSignStatus(params.fgStatus)),
fgLapor: params.fgLapor,
revNo: params.revNo,
thnPajak: params.tahunPajak,
msPajak: params.masaPajak,
kdObjPjk: params.kodeObjekPajak,
noBupot: params.noBupot,
idDipotong: params.userId,
glAccount: params.glAccount,
namaDipotong: params.nama,
jmlBruto: params.dpp,
pphDipotong: params.pphDipotong,
created: params.created_by,
fgKirimEmail: params.fgkirimemail,
created_at: params.created_at,
updated: params.updated_by,
updated_at: params.updated_at,
});
const normalisPropsParmasGetDn = (params: any) => {
const sorting = !isEmpty(params.sort) ? transformSortModelToSortApiPayload(params.sort) : {};
return {
...params,
page: params.Page,
limit: params.Limit,
masaPajak: params.msPajak || null,
tahunPajak: params.thnPajak || null,
npwp: params.idDipotong || null,
advanced: isEmpty(params.advanced) ? undefined : params.advanced,
...sorting,
};
};
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) });
return {
...response,
// data: response.data.map((data) => normalisePropsGetDn(data)),
};
},
initialData: {
data: [],
total: 0,
},
refetchOnWindowFocus: false,
...props,
});
return query;
};
export default useGetBulanan;
import { useEffect } from 'react';
import { useFormContext, useWatch } from 'react-hook-form';
import type { TGetListDataKOPDn } from '../types/types';
const usePphDipotong = (kodeObjekPajakSelected?: TGetListDataKOPDn) => {
const { watch, setValue, control } = useFormContext();
// ambil value dari form
const fgFasilitas = watch('fgFasilitas');
const fgIdDipotong = watch('fgIdDipotong');
// mapping statusPPh ke isFinal
const isFinal = kodeObjekPajakSelected?.statuspph?.toLowerCase() === 'final' ? 1 : 0;
const updateTarifValues = () => {
if (kodeObjekPajakSelected) {
let valueTarif = Number(kodeObjekPajakSelected.tarif) || 0;
if (fgFasilitas === '6') {
valueTarif = 0.5;
} else if (fgFasilitas === '8') {
valueTarif = 0;
}
setValue('tarif', valueTarif, { shouldValidate: true });
setValue('tarifLt', fgIdDipotong === '1' && isFinal === 0 ? '100' : '0', {
shouldValidate: true,
});
}
};
// watch field yang mempengaruhi perhitungan
const handlerSetPphDipotong = useWatch({
control,
name: ['thnPajak', 'fgFasilitas', 'fgIdDipotong', 'jmlBruto', 'tarif'],
});
const calculateAndSetPphDipotong = (
thnPajak: number,
fgFasilitas: string,
fgIdDipotong: string,
jmlBruto: number,
tarif: number
) => {
if (kodeObjekPajakSelected) {
const valTarif = thnPajak < 2024 && fgIdDipotong === '1' && isFinal === 0 ? tarif * 2 : tarif;
const valPphDipotong =
fgFasilitas === '8' // contoh: fasilitas tertentu PPh 0
? 0
: (jmlBruto * valTarif) / 100;
setValue('pphDipotong', Math.round(valPphDipotong || 0), {
shouldValidate: true,
});
}
};
useEffect(() => {
if (handlerSetPphDipotong.filter((item) => !item).length < 2) {
calculateAndSetPphDipotong(
Number(handlerSetPphDipotong[0]),
handlerSetPphDipotong[1] as string,
handlerSetPphDipotong[2] as string,
Number(handlerSetPphDipotong[3]),
Number(handlerSetPphDipotong[4])
);
}
}, [handlerSetPphDipotong]);
return {
updateTarifValues,
};
};
export default usePphDipotong;
import { useMutation } from '@tanstack/react-query';
import dayjs from 'dayjs';
import dnApi from '../utils/api';
import type { TPostDnRequest } from '../types/types';
const transformParams = ({ isPengganti = false, ...dnData }: any): TPostDnRequest => {
const {
id,
idBupot,
noBupot,
msPajak,
thnPajak,
idDipotong,
nitku,
namaDipotong,
fgFasilitas,
noDokLainnya,
kdObjPjk,
kdJnsPjk,
statusPph,
jmlBruto,
tarif,
pphDipotong,
kap,
kjs,
revNo: initialRevNo,
tglPemotongan,
namaDok,
nomorDok,
tglDok,
metodePembayaranBendahara,
nomorSP2D,
idTku,
email,
glAccount,
keterangan1,
keterangan2,
keterangan3,
keterangan4,
keterangan5,
} = dnData;
const dokReferensi = [
{
dokReferensi: namaDok || '',
nomorDokumen: nomorDok || '',
tanggal_Dokumen: tglDok ? dayjs(tglDok).format('DDMMYYYY') : '',
metodePembayaranBendahara: metodePembayaranBendahara || '',
nomorSP2D: nomorSP2D || '',
},
];
const revNo = isPengganti ? parseInt(initialRevNo || 0, 10) + 1 : parseInt(initialRevNo || 0, 10);
const npwpLog = localStorage.getItem('npwp_log') ?? '';
return {
id: !isPengganti ? (id ?? null) : null,
idBupot: idBupot ?? '',
noBupot: noBupot ?? '',
npwpPemotong: npwpLog,
idTku: idTku ?? '',
masaPajak: msPajak ? dayjs(msPajak).format('MM') : '',
tahunPajak: thnPajak ? Number(dayjs(thnPajak).format('YYYY')) : 0,
npwp: idDipotong ?? '',
nik: nitku ?? (idDipotong ? `${idDipotong}000000` : ''),
nama: namaDipotong ?? '',
revNo,
fgNpwpNik: 'true', // static
fgJnsBupot: 'BPU', // static
dataDetilBpu: {
sertifikatInsentifDipotong: fgFasilitas ?? '9',
nomorSertifikatInsentif: noDokLainnya ?? '',
kodeObjekPajak: kdObjPjk ?? '',
pasalPPh: kdJnsPjk ?? '',
statusPPh: statusPph ?? '',
dpp: jmlBruto ?? '',
tarif: tarif ?? '',
pphDipotong: pphDipotong ?? '',
kap: kap ?? '',
kjs: kjs ?? '',
dokReferensi,
},
tglPemotongan: tglPemotongan ? dayjs(tglPemotongan).format('DDMMYYYY') : '',
email: email ?? '',
glAccount: glAccount ?? '',
keterangan1: keterangan1 ?? '',
keterangan2: keterangan2 ?? '',
keterangan3: keterangan3 ?? '',
keterangan4: keterangan4 ?? '',
keterangan5: keterangan5 ?? '',
};
};
const useSaveBulanan = (props?: any) =>
useMutation({
mutationKey: ['Save-Dn'],
mutationFn: (params: any) => dnApi.saveDn(transformParams(params)),
...props,
});
export default useSaveBulanan;
export type TBaseResponseAPI<T> = {
status: string;
message: string;
data: T;
time: string;
code: number;
metaPage: TBaseResponseMetaPage;
total?: number;
};
type TResponseData = {};
type TBaseResponseMetaPage = {
pageNum: number | null;
rowPerPage: number | null;
totalRow: number;
};
export type TGetListDataTableDn = {
id: number;
npwpPemotong: string;
idTku: string;
masaPajak: string;
tahunPajak: string;
fgNpwpNik: string;
npwp: string;
nik: string;
nama: string;
sertifikatInsentifDipotong: string;
nomorSertifikatInsentif: string;
kodeObjekPajak: string;
pasalPPh: string;
statusPPh: string;
dpp: string;
tarif: string;
pphDipotong: string;
kap: string;
kjs: string;
tglpemotongan: string;
userId: string;
created_at: string;
updated_at: string;
created_by: string;
updated_by: string;
fgStatus: string;
internal_id: string;
dokumen_referensi: {
dokReferensi: string;
nomorDokumen: string;
tanggal_Dokumen: string;
metodePembayaranBendahara: string;
nomorSP2D: string;
}[];
revNo: number;
noBupot: string | null;
idBupot: string | null;
npwpNikPenandatangan: string;
namaPenandatangan: string;
link: string | null;
errorMsg: string | null;
email: string | null;
glAccount: string;
fgkirimemail: string;
glName: string | null;
keterangan1: string | null;
keterangan2: string | null;
keterangan3: string | null;
keterangan4: string | null;
keterangan5: string | null;
fgLapor: string;
};
export type TGetListDataTableDnResult = TGetListDataTableDn[];
export type TGetListDataKOPDn = {
dtp: number;
kap: string;
kjs: string;
kode: string;
nama: string;
noCertificate: number;
normanetto: string;
otherCert: number;
pasal: string;
skbBungaTabungan: number;
skbPHTB: number;
skbPasal22: number;
skbPasal23: number;
statuspph: string;
suket: number;
tarif: string;
};
export type TGetListDataKOPDnResult = TGetListDataKOPDn[];
export type ActionItem = {
title: string;
icon: React.ReactNode;
func?: () => void;
disabled?: boolean;
};
export type TPostDnRequest = {
id: string | null;
idBupot: string;
noBupot: string;
npwpPemotong: string;
idTku: string;
masaPajak: string;
tahunPajak: number;
npwp: string;
nik: string;
nama: string;
revNo: number;
fgNpwpNik: string; // static "true"
fgJnsBupot: string; // static "BPU"
dataDetilBpu: {
sertifikatInsentifDipotong: string;
nomorSertifikatInsentif: string;
kodeObjekPajak: string;
pasalPPh: string;
statusPPh: string;
dpp: string;
tarif: string;
pphDipotong: string;
kap: string;
kjs: string;
dokReferensi: {
dokReferensi: string;
nomorDokumen: string;
tanggal_Dokumen: string;
metodePembayaranBendahara: string;
nomorSP2D: string;
}[];
};
tglPemotongan: string;
email: string;
glAccount: string;
keterangan1: string;
keterangan2: string;
keterangan3: string;
keterangan4: string;
keterangan5: string;
};
import type {
TBaseResponseAPI,
TGetListDataKOPDnResult,
TGetListDataTableDnResult,
TPostDnRequest,
} 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);
}
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>>(
'/sandbox/mst_kop_bpu',
{ params } // ⬅️ inject ke query string
);
const body = response.data;
if (response.status !== 200 || body.status !== 'success') {
throw new Error(body.message);
}
return body;
};
bulananApi.saveBulanan = async (config: TPostDnRequest) => {
const {
data: { message, data },
status: statusCode,
} = await bulananClient.post<TBaseResponseAPI<TPostDnRequest>>('/IF_TXR_028/a0', {
...config,
});
if (statusCode !== 200) {
throw new Error(message);
}
return 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;
import dayjs from 'dayjs';
import { ActionRekam, MIN_THN_PAJAK } from '../constant';
export const currentYear = dayjs().year();
export const getHighestStartingYear = (thnAwalUnifikasi: any) =>
Math.max(MIN_THN_PAJAK, thnAwalUnifikasi);
export const selectedInitialMonth = ({ thnAwalUnifikasi, masaAwalUnifikasi }: any) => {
const highestYear = getHighestStartingYear(thnAwalUnifikasi);
return highestYear > thnAwalUnifikasi ? '01' : masaAwalUnifikasi;
};
export const determineStartingMonth = ({ thnPajak, thnAwalUnifikasi, masaAwalUnifikasi }: any) => {
const highestYear = getHighestStartingYear(thnAwalUnifikasi);
const initialMonth = selectedInitialMonth({ thnAwalUnifikasi, masaAwalUnifikasi });
return thnPajak >= highestYear && thnPajak <= currentYear ? initialMonth : '';
};
export const checkCurrentPage = (pathname: string) => {
if (pathname.includes('rekam')) return ActionRekam.REKAM;
if (pathname.includes('pengganti')) return ActionRekam.PENGGANTI;
if (pathname.includes('pembetulan')) return ActionRekam.PEMBETULAN;
if (pathname.includes('ubah')) return ActionRekam.UBAH;
return ActionRekam.REKAM;
};
import Button from '@mui/material/Button';
import { CustomBreadcrumbs } from 'src/components/custom-breadcrumbs';
import { DashboardContent } from 'src/layouts/dashboard';
import { RouterLink } from 'src/routes/components';
import { paths } from 'src/routes/paths';
import type { GridColDef, GridFilterModel, GridSortModel } from '@mui/x-data-grid-premium';
import { DataGridPremium } from '@mui/x-data-grid-premium';
import { useMemo, useState } from 'react';
import TableHeaderLabel from 'src/shared/components/TableHeaderLabel';
import useGetBulanan from '../hooks/useGetBulanan';
// import CustomToolbarDn from '../components/customToolbarDn';
// import CustomToolbar, { CustomFilterButton } from '../components/customToolbarDn2';
export type IColumnGrid = GridColDef & {
field:
| 'fgStatus'
| 'noBupot'
| 'masaPajak'
| 'tahunPajak'
| 'kdObjPjk'
| 'pasalPPh'
| 'npwp'
| 'nama'
| 'dpp'
| 'pphDipotong'
| 'idTku'
| 'dokReferensi'
| 'nomorDokumen'
| 'created_by'
| 'created_at'
| 'updated_by'
| 'updated_at'
| 'internal_id'
| 'keterangan1'
| 'keterangan2'
| 'keterangan3'
| 'keterangan4'
| 'keterangan5';
valueOptions?: string[];
};
export function BulananListView() {
// const [tabs1, setTabs1] = useState<number>(1);
// const [tabs2, setTabs2] = useState<number>(0);
const [paginationModel, setPaginationModel] = useState({
page: 0, // 0-based index
pageSize: 10,
});
const [filterModel, setFilterModel] = useState<GridFilterModel>({ items: [] });
const [sortModel, setSortModel] = useState<GridSortModel>([]);
const [rowSelectionModel, setRowSelectionModel] = useState<any>([]);
// const [rowSelectionModel, setRowSelectionModel] =
// useState<GridRowSelectionModel>(new Set<GridRowId>());
// const navigate = useNavigate();
// Enhance tabs dengan navigate
// const enhancedTabsTop = TABS_TOP_UNIFIKASI.map((tab) =>
// tab.value ? { ...tab, onClick: () => navigate(tab.value) } : tab
// );
// const enhancedTabsChoice = TABS_CHOICE.map((tab) => ({
// ...tab,
// onClick: () => navigate(tab.value),
// }));
const buildAdvancedFilter = (filters?: GridFilterModel['items']) => {
if (!filters || filters.length === 0) return '';
return filters
.map((f) => {
if (!f.value || !f.field) return null;
const field = `LOWER("${f.field}")`;
const val = String(f.value).toLowerCase();
switch (f.operator) {
case 'contains':
return `${field} LIKE '%${val}%'`;
case 'equals':
case 'is':
return `${field} = '${val}'`;
case 'isNot':
return `${field} <> '${val}'`;
default:
return null;
}
})
.filter(Boolean)
.join(' AND ');
};
const { data, isLoading } = useGetBulanan({
params: {
Page: paginationModel.page + 1, // API biasanya 1-based
Limit: paginationModel.pageSize,
advanced: buildAdvancedFilter(filterModel?.items),
sortingMode: sortModel[0]?.field,
sortingMethod: sortModel[0]?.sort,
},
refetchOnWindowFocus: false,
});
const totalRows = data?.total || 0;
const rows = useMemo(() => data?.data || [], [data?.data]);
console.log(data, '123');
// const handleChange = (event: React.SyntheticEvent, newValue: number) => {
// setTabs1(newValue);
// };
// const handleChange2 = (event: React.SyntheticEvent, newValue: number) => {
// setTabs2(newValue);
// };
// type aman
type Status = 'draft' | 'normal' | 'cancelled' | 'amendment';
type StatusOption = {
value: Status;
label: string;
};
const statusOptions: StatusOption[] = [
{ value: 'draft', label: 'Draft' },
{ value: 'normal', label: 'Normal' },
{ value: 'cancelled', label: 'Dibatalkan' },
{ value: 'amendment', label: 'Normal Pengganti' },
];
const columns: IColumnGrid[] = [
{
field: 'fgStatus',
headerName: 'Status',
width: 300,
type: 'singleSelect',
valueOptions: statusOptions.map((opt) => opt.value), // filter dropdown pakai value
valueFormatter: (params: any) => {
const option = statusOptions.find((opt) => opt.value === params.value);
return option ? option.label : (params.value as string);
},
},
{ field: 'noBupot', headerName: 'Nomor Bukti Pemotongan', width: 200 },
{ field: 'masaPajak', headerName: 'Masa Pajak', width: 150 },
{ field: 'tahunPajak', headerName: 'Tahun Pajak', width: 150 },
{ field: 'kdObjPjk', headerName: 'Kode Objek Pajak', width: 150 },
{ field: 'npwp', headerName: 'Identitas', width: 150 },
{ field: 'nama', headerName: 'Nama', width: 150 },
{ field: 'dpp', headerName: 'Jumlah Penghasilan Bruto (Rp)', width: 150 },
{ field: 'pphDipotong', headerName: 'Jumlah PPh Terutang (Rp)', width: 200 },
{ field: 'idTku', headerName: 'NITKU Pemotong', width: 150 },
{ field: 'dokReferensi', headerName: 'Nama dokumen', width: 150 },
{ field: 'nomorDokumen', headerName: 'Nomor dokumen', width: 150 },
{ field: 'created_by', headerName: 'Created', width: 150 },
{ field: 'created_at', headerName: 'Created At', width: 200 },
{ field: 'updated_by', headerName: 'Updated', width: 150 },
{ field: 'updated_at', headerName: 'Update At', width: 150 },
{ field: 'internal_id', headerName: 'Referensi', width: 150 },
{ field: 'keterangan1', headerName: 'Keterangan 1', width: 150 },
{ field: 'keterangan2', headerName: 'Keterangan 2', width: 200 },
{ field: 'keterangan3', headerName: 'Keterangan 3', width: 150 },
{ field: 'keterangan4', headerName: 'Keterangan 4', width: 150 },
{ field: 'keterangan5', headerName: 'Keterangan 5', width: 150 },
];
return (
<DashboardContent>
<CustomBreadcrumbs
heading="Bupot Bulanan"
links={[
{ name: 'Dashboard', href: paths.dashboard.root },
{ name: 'e-Bupot PPh Pasal 21 Bulanan' },
]}
action={
<Button component={RouterLink} href={paths.pph21.bulananRekam} variant="contained">
Rekam Data
</Button>
}
/>
<TableHeaderLabel label="Daftar Bupot Bulanan" />
<DataGridPremium
sx={{
border: 1,
borderColor: 'divider',
borderRadius: 2,
'& .MuiDataGrid-cell': {
borderColor: 'divider',
},
'& .MuiDataGrid-columnHeaders': {
borderColor: 'divider',
},
}}
checkboxSelection
rows={rows}
columns={columns}
loading={isLoading}
rowCount={totalRows}
initialState={{
pagination: { paginationModel: { pageSize: 10, page: 0 } },
}}
pagination
pageSizeOptions={[5, 10, 15, 25, 50, 100, 250, 500, 750, 100]}
paginationMode="server"
onPaginationModelChange={setPaginationModel}
filterMode="server"
onFilterModelChange={setFilterModel}
sortingMode="server"
onSortModelChange={setSortModel}
pinnedColumns={{
left: ['__check__', 'fgStatus', 'noBupot'],
}}
cellSelection
// slots={{
// toolbar: () => (
// <CustomToolbar
// actions={[
// [
// {
// title: 'Edit',
// icon: <EditNoteTwoTone sx={{ width: 26, height: 26 }} />,
// func: () => {},
// disabled: true,
// },
// {
// title: 'Detail',
// icon: <ArticleTwoTone sx={{ width: 26, height: 26 }} />,
// func: () => {},
// disabled: true,
// },
// {
// title: 'Hapus',
// icon: <DeleteSweepTwoTone sx={{ width: 26, height: 26 }} />,
// func: () => {},
// disabled: false,
// },
// ],
// [
// {
// title: 'Upload',
// icon: <UploadFileTwoTone sx={{ width: 26, height: 26 }} />,
// func: () => {},
// disabled: false,
// },
// {
// title: 'Pengganti',
// icon: <FileOpenTwoTone sx={{ width: 26, height: 26 }} />,
// func: () => {},
// disabled: false,
// },
// {
// title: 'Batal',
// icon: <HighlightOffTwoTone sx={{ width: 26, height: 26 }} />,
// func: () => {},
// disabled: true,
// },
// ],
// ]}
// columns={columns}
// filterModel={filterModel}
// setFilterModel={setFilterModel}
// statusOptions={statusOptions.map((s) => ({ value: s.value, label: s.label }))}
// />
// ),
// }}
/>
</DashboardContent>
);
}
import { zodResolver } from '@hookform/resolvers/zod';
import { LoadingButton } from '@mui/lab';
import Grid from '@mui/material/Grid';
import { Suspense, useMemo, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { CustomBreadcrumbs } from 'src/components/custom-breadcrumbs';
import { DashboardContent } from 'src/layouts/dashboard';
import { paths } from 'src/routes/paths';
import HeadingRekam from 'src/shared/components/HeadingRekam';
import z from 'zod';
import Divider from '@mui/material/Divider';
import FormSkeleton from 'src/shared/skeletons/FormSkeleton';
import Agreement from 'src/shared/components/agreement/Agreement';
import Stack from '@mui/material/Stack';
import PanduanDnRekam from '../components/rekam/PanduanDnRekam';
import { useParams, usePathname } from 'src/routes/hooks';
import Identitas from '../components/rekam/Identitas';
import { checkCurrentPage } from '../utils/utils';
import PerhitunganPPhPasal21 from '../components/rekam/PerhitunganPPhPasal21';
import { KODE_OBJEK_PAJAK, KODE_OBJEK_PAJAK_TEXT } from '../constant';
import JumlahPerhitunganForm from '../components/rekam/JumlahPerhitunganForm';
import { Field } from 'src/components/hook-form';
const bulananSchema = z.object({
tglPemotongan: z.string().nonempty('Tanggal Pemotongan wajib diisi'),
thnPajak: z.string().nonempty('Tahun Pajak wajib diisi'),
msPajak: z.string().nonempty('Masa Pajak wajib diisi'),
nitku: z
.string()
.nonempty('NITKU wajib diisi')
.regex(/^\d{22}$/, 'NITKU harus 22 digit'),
namaDipotong: z.string().nonempty('Nama wajib diisi'),
idDipotong: z
.string()
.nonempty('NPWP wajib diisi')
.regex(/^\d{16}$/, 'NPWP harus 16 digit'),
email: z.string().optional(),
noDokLainnya: z.string().nonempty('No Dokumen Lainnya wajib diisi'),
// bisa tambah field lain sesuai kebutuhan
keterangan1: z.string().optional(),
keterangan2: z.string().optional(),
keterangan3: z.string().optional(),
keterangan4: z.string().optional(),
keterangan5: z.string().optional(),
});
export const BulananRekamView = () => {
const { id } = useParams();
const pathname = usePathname();
const [isOpenPanduan, setIsOpenPanduan] = useState<boolean>(false);
const [isCheckedAgreement, setIsCheckedAgreement] = useState<boolean>(false);
const actionRekam = checkCurrentPage(pathname);
const dataListKOP = useMemo(
() =>
[KODE_OBJEK_PAJAK.BULANAN_01, KODE_OBJEK_PAJAK.BULANAN_02, KODE_OBJEK_PAJAK.BULANAN_03].map(
(value) => ({
value,
label: `${value} : ${KODE_OBJEK_PAJAK_TEXT[value]}`,
})
),
[]
);
type BpuFormData = z.infer<typeof bulananSchema>;
const handleOpenPanduan = () => setIsOpenPanduan(!isOpenPanduan);
const defaultValues = {
tglPemotongan: '',
thnPajak: '',
msPajak: '',
idDipotong: '',
nitku: '',
namaDipotong: '',
email: '',
keterangan1: '',
keterangan2: '',
keterangan3: '',
keterangan4: '',
keterangan5: '',
kdObjPjk: '',
fgFasilitas: '',
noDokLainnya: '',
jmlBruto: 0,
tarif: '',
PerhitunganPPhPasal21: '',
namaDok: '',
nomorDok: '',
tglDok: '',
idTku: '',
};
const methods = useForm<BpuFormData>({
mode: 'all',
resolver: zodResolver(bulananSchema),
defaultValues,
});
const SubmitRekam = () => {
console.log('Submit API');
};
const MockNitku = [
{
nama: '1091031210912281000000',
},
{
nama: '1091031210912281000001',
},
];
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' },
]}
/>
<HeadingRekam label="rekam Bupot PPh Pasal 21 Bulanan" />
<Grid container columnSpacing={2} /* container otomatis */>
<Grid size={{ xs: isOpenPanduan ? 8 : 11 }}>
<form onSubmit={methods.handleSubmit(SubmitRekam)}>
<FormProvider {...methods}>
<Suspense fallback={<FormSkeleton />}>
<Identitas />
<Suspense fallback={<FormSkeleton />}>
<PerhitunganPPhPasal21 kodeObjectPajak={dataListKOP} />
</Suspense>
<JumlahPerhitunganForm />
<Grid size={{ md: 12 }}>
<Field.Autocomplete
name="idTku"
label="NITKU Pemotong"
options={MockNitku.map((a) => ({ value: a, label: a }))}
/>
</Grid>
<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>
<Stack direction="row" gap={2} justifyContent="end" marginTop={2}>
<LoadingButton
type="submit"
// loading={saveDn.isLoading}
disabled={!isCheckedAgreement}
variant="outlined"
sx={{ color: '#143B88' }}
>
Save as Draft
</LoadingButton>
<LoadingButton
type="button"
disabled={!isCheckedAgreement}
// onClick={handleClickUploadSsp}
// loading={uploadDn.isLoading}
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>
</DashboardContent>
);
};
export * from './bulanan-list-view';
export * from './bulanan-rekam-view';
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