Commit 1748cbbe authored by Rais Aryaguna's avatar Rais Aryaguna

feat: Add new constants and refactor hooks for Bulanan Bupot

- Introduced METODE_POTONG constants for payment methods.
- Updated useGetBulanan hook to normalize data structure and improve readability.
- Refactored useSaveBulanan hook to handle new data structure and API calls.
- Enhanced types in types.ts for better type safety and clarity.
- Updated API utility functions to align with new data structures.
- Improved BulananListView to display additional fields and formatted values.
- Refactored BulananRekamView to include new validation schema and handle form submissions.
- Changed fetcher utility in hitung.ts to use the correct endpoint for calculations.
parent a7813084
...@@ -58,10 +58,10 @@ const { ...@@ -58,10 +58,10 @@ const {
} = endpoints.masterData; } = endpoints.masterData;
export function useKodeNegara(): UseKodeNegaraReturn { export function useKodeNegara(): UseKodeNegaraReturn {
const { data, isLoading, error } = useQuery<KodeNegaraResponse, AxiosError>({ const { data, isLoading, error } = useQuery<KodeNegara[], AxiosError>({
queryKey: ['kodeNegara'], queryKey: ['kodeNegara'],
queryFn: async () => { queryFn: async () => {
const response = await fetcher<ApiResponse<KodeNegaraResponse>>(kodeNegara); const response = await fetcher<ApiResponse<KodeNegara[]>>(kodeNegara);
return validateResponse(response); return validateResponse(response);
}, },
...@@ -69,7 +69,7 @@ export function useKodeNegara(): UseKodeNegaraReturn { ...@@ -69,7 +69,7 @@ export function useKodeNegara(): UseKodeNegaraReturn {
}); });
return { return {
kodeNegara: data?.data || [], kodeNegara: data || [],
kodeNegaraLoading: isLoading, kodeNegaraLoading: isLoading,
kodeNegaraError: error, kodeNegaraError: error,
}; };
......
...@@ -24,6 +24,7 @@ import { AuthProvider as FirebaseAuthProvider } from 'src/auth/context/firebase' ...@@ -24,6 +24,7 @@ import { AuthProvider as FirebaseAuthProvider } from 'src/auth/context/firebase'
import { store, persistor } from 'src/store'; import { store, persistor } from 'src/store';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { PersistGate } from 'redux-persist/integration/react'; import { PersistGate } from 'redux-persist/integration/react';
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
...@@ -69,6 +70,7 @@ export default function App({ children }: AppProps) { ...@@ -69,6 +70,7 @@ export default function App({ children }: AppProps) {
</SettingsProvider> </SettingsProvider>
</AuthProvider> </AuthProvider>
</I18nProvider> </I18nProvider>
<ReactQueryDevtools />
</QueryClientProvider> </QueryClientProvider>
</PersistGate> </PersistGate>
</Provider> </Provider>
......
// ---------------------------------------------------------------------- // ----------------------------------------------------------------------
export const JWT_STORAGE_KEY = 'jwt_access_token'; export const JWT_STORAGE_KEY = 'jwt_access_token';
export const X_TOKEN = 'x-token';
import type { AxiosRequestConfig, AxiosInstance } from 'axios'; import type { AxiosRequestConfig, AxiosInstance } from 'axios';
import axios from 'axios'; import axios from 'axios';
import { JWT_STORAGE_KEY } from 'src/auth/context/jwt'; import { JWT_STORAGE_KEY, X_TOKEN } from 'src/auth/context/jwt';
import { CONFIG } from 'src/global-config'; import { CONFIG } from 'src/global-config';
const API_CONFIGS = { const API_CONFIGS = {
...@@ -43,9 +43,11 @@ const createAxiosInstance = ( ...@@ -43,9 +43,11 @@ const createAxiosInstance = (
config.headers.Authorization = `Basic ${credentials}`; config.headers.Authorization = `Basic ${credentials}`;
} else { } else {
// Bearer Token untuk Node API // Bearer Token untuk Node API
const accessToken = localStorage.getItem(JWT_STORAGE_KEY); const accessToken = localStorage.getItem('jwt_access_token');
if (accessToken) { const xToken = localStorage.getItem('x-token');
if (accessToken && xToken) {
config.headers.Authorization = `Bearer ${accessToken}`; config.headers.Authorization = `Bearer ${accessToken}`;
config.headers['x-token'] = xToken;
} }
} }
return config; return config;
...@@ -71,8 +73,8 @@ export const axiosnodesandbox = createAxiosInstance( ...@@ -71,8 +73,8 @@ export const axiosnodesandbox = createAxiosInstance(
API_CONFIGS.nodesandbox.name API_CONFIGS.nodesandbox.name
); );
export const axiosApiJava = createAxiosInstance( export const axiosHitung = createAxiosInstance(
API_CONFIGS.apiJava.baseURL, `${API_CONFIGS.apiJava.baseURL}:${API_CONFIGS.apiJava.portPPH21}`,
API_CONFIGS.apiJava.name, API_CONFIGS.apiJava.name,
{ {
useBasicAuth: true, useBasicAuth: true,
...@@ -103,12 +105,12 @@ export const fetcher = async <T = unknown>(args: FetcherArgs): Promise<T> => { ...@@ -103,12 +105,12 @@ export const fetcher = async <T = unknown>(args: FetcherArgs): Promise<T> => {
} }
}; };
export const fetcherJava = async <T = unknown>(args: FetcherArgs): Promise<T> => { export const fetcherHitung = async <T = unknown>(args: FetcherArgs): Promise<T> => {
try { try {
const [url, config = {}] = Array.isArray(args) ? args : [args, {}]; const [url, config = {}] = Array.isArray(args) ? args : [args, {}];
const { method = 'GET', ...restConfig } = config; const { method = 'GET', ...restConfig } = config;
const res = await axiosApiJava.request<T>({ const res = await axiosHitung.request<T>({
url, url,
method, method,
...restConfig, ...restConfig,
......
...@@ -16,6 +16,7 @@ const Identitas = ({ isPengganti }: IdentitasProps) => { ...@@ -16,6 +16,7 @@ const Identitas = ({ isPengganti }: IdentitasProps) => {
const { dnId } = useParams(); const { dnId } = useParams();
const { setValue, watch } = useFormContext(); const { setValue, watch } = useFormContext();
const tanggalPemotongan = watch('tglPemotongan'); const tanggalPemotongan = watch('tglPemotongan');
const fgKaryawanAsing = watch('fgKaryawanAsing');
const [jumlahKeterangan, setJumlahKeterangan] = useState<number>(0); const [jumlahKeterangan, setJumlahKeterangan] = useState<number>(0);
const maxKeterangan = 5; const maxKeterangan = 5;
...@@ -60,10 +61,16 @@ const Identitas = ({ isPengganti }: IdentitasProps) => { ...@@ -60,10 +61,16 @@ const Identitas = ({ isPengganti }: IdentitasProps) => {
/> />
</Grid> </Grid>
<Grid size={{ md: 3 }}> <Grid size={{ md: 3 }}>
<Field.DatePicker name="thnPajak" label="Tahun Pajak" view="year" format="YYYY" /> <Field.DatePicker
name="thnPajak"
label="Tahun Pajak"
view="year"
format="YYYY"
readOnly
/>
</Grid> </Grid>
<Grid size={{ md: 3 }}> <Grid size={{ md: 3 }}>
<Field.DatePicker name="msPajak" label="Masa Pajak" view="month" format="MM" /> <Field.DatePicker name="msPajak" label="Masa Pajak" view="month" format="MM" readOnly />
</Grid> </Grid>
{/* NPWP dengan onChange langsung */} {/* NPWP dengan onChange langsung */}
...@@ -73,6 +80,7 @@ const Identitas = ({ isPengganti }: IdentitasProps) => { ...@@ -73,6 +80,7 @@ const Identitas = ({ isPengganti }: IdentitasProps) => {
label="NPWP" label="NPWP"
onChange={(e) => { onChange={(e) => {
const value = e.target.value.replace(/\D/g, '').slice(0, 16); // hanya angka, max 16 const value = e.target.value.replace(/\D/g, '').slice(0, 16); // hanya angka, max 16
console.log('🚀 ~ value:', value);
setValue('idDipotong', value, { shouldValidate: true, shouldDirty: true }); setValue('idDipotong', value, { shouldValidate: true, shouldDirty: true });
setValue('nitku', value.length === 16 ? value + '000000' : value, { setValue('nitku', value.length === 16 ? value + '000000' : value, {
shouldValidate: true, shouldValidate: true,
...@@ -100,11 +108,20 @@ const Identitas = ({ isPengganti }: IdentitasProps) => { ...@@ -100,11 +108,20 @@ const Identitas = ({ isPengganti }: IdentitasProps) => {
<Field.Autocomplete <Field.Autocomplete
name="kodeNegara" name="kodeNegara"
label="Negara" label="Negara"
options={kodeNegara.map((val) => ({ label: val.nama, ...val }))} options={kodeNegara.map((val) => ({ label: val.nama, value: val.kode }))}
readOnly={!fgKaryawanAsing}
/> />
</Grid> </Grid>
<Grid size={{ md: 6 }}> <Grid size={{ md: 6 }}>
<Field.Text name="passport" label="Paspor" /> <Field.Text
name="passport"
label="Paspor"
slotProps={{
input: {
readOnly: !fgKaryawanAsing,
},
}}
/>
</Grid> </Grid>
</Grid> </Grid>
......
import { CalculateRounded } from '@mui/icons-material';
import { LoadingButton } from '@mui/lab';
import { Grid } from '@mui/material'; import { Grid } from '@mui/material';
import dayjs from 'dayjs';
import { useMemo } from 'react';
import { useFormContext } from 'react-hook-form';
import { Field } from 'src/components/hook-form'; import { Field } from 'src/components/hook-form';
import { RHFNumeric } from 'src/components/hook-form/rhf-numeric';
import { getHitungBulananErrorMessage, useHitungBulanan } from 'src/sections/bupot-21-26/hitung';
import { import {
FG_FASILITAS_PPH_21, FG_FASILITAS_PPH_21,
FG_PERHITUNGAN, FG_PERHITUNGAN,
FG_PERHITUNGAN_TEXT, FG_PERHITUNGAN_TEXT,
KODE_OBJEK_PAJAK, KODE_OBJEK_PAJAK,
METODE_POTONG,
PERHITUNGAN_BUPOT21,
PTKP, PTKP,
PTKP_TEXT, PTKP_TEXT,
PTKP_TITLE, PTKP_TITLE,
} from '../../constant'; } 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) => ({ const fgPerhitunganOptions = Object.values(FG_PERHITUNGAN).map((value) => ({
value, value,
...@@ -21,9 +25,8 @@ const fgPerhitunganOptions = Object.values(FG_PERHITUNGAN).map((value) => ({ ...@@ -21,9 +25,8 @@ const fgPerhitunganOptions = Object.values(FG_PERHITUNGAN).map((value) => ({
})); }));
function JumlahPerhitunganForm() { function JumlahPerhitunganForm() {
const { watch } = useFormContext(); const { watch, getValues, setValue } = useFormContext();
const fgFasilitas = watch('fgFasilitas');
const ptkpOptions = useMemo( const ptkpOptions = useMemo(
() => () =>
Object.entries(PTKP) Object.entries(PTKP)
...@@ -31,6 +34,41 @@ function JumlahPerhitunganForm() { ...@@ -31,6 +34,41 @@ function JumlahPerhitunganForm() {
.filter((option) => !option.value.includes(PTKP_TITLE.HB)), .filter((option) => !option.value.includes(PTKP_TITLE.HB)),
[] []
); );
const { mutate, isPending } = useHitungBulanan({
onSuccess: (data) => {
console.log('✅ Berhasil hitung PPh21:', data);
setValue(
'pph21',
[FG_FASILITAS_PPH_21.SKB_PPH_PASAL_21].includes(watch('fgFasilitas')) ? 0 : data.pphBulan
);
setValue('dpp', 100);
setValue('tarif', data.tarif);
setValue('tunjanganPPh', data.pph21ditanggungperusahaan ?? data.tunjanganPPh);
},
onError: (error) => {
console.error('❌ Error:', getHitungBulananErrorMessage(error));
},
});
const handleHitung = () => {
const { phBruto: penghasilanBruto, ptkp, fgPerhitungan, tglPemotongan, kdObjPjk } = getValues();
const tglBupot = dayjs(tglPemotongan).format('DDMMYYYY');
const data = {
status: ptkp.value,
kodeObjekPajak: kdObjPjk.value,
tglBupot,
penghasilanKotor: penghasilanBruto,
fgPerhitungan,
metode: fgPerhitungan !== '0' ? METODE_POTONG.GROSS_UP : METODE_POTONG.GROSS, // Simplify conditional assignment
};
mutate(data as any);
};
return ( return (
<Grid container rowSpacing={2} columnSpacing={2} sx={{ my: 3 }}> <Grid container rowSpacing={2} columnSpacing={2} sx={{ my: 3 }}>
<Grid size={{ md: 3 }}> <Grid size={{ md: 3 }}>
...@@ -51,32 +89,15 @@ function JumlahPerhitunganForm() { ...@@ -51,32 +89,15 @@ function JumlahPerhitunganForm() {
</Grid> </Grid>
<Grid size={{ md: 6 }}> <Grid size={{ md: 6 }}>
<RHFNumeric name="tunjanganPPh" label="Tunjangan PPh 21 (Rp)" /> <RHFNumeric name="tunjanganPPh" label="Tunjangan PPh 21 (Rp)" readOnly />
</Grid> </Grid>
<Grid size={{ md: 5 }}> <Grid size={{ md: 5 }}>
<RHFNumeric <RHFNumeric name="tarif" label="Tarif (%)" allowDecimalValue maxValue={100} readOnly />
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>
<Grid size={{ md: 5 }}> <Grid size={{ md: 5 }}>
<RHFNumeric <RHFNumeric name="pph21" label="PPh Pasal 21" readOnly />
name="pph21"
label="PPh Pasal 21"
readOnly={![KODE_OBJEK_PAJAK.FINAL_38].includes(watch('kodeObjekPajak'))}
/>
</Grid> </Grid>
<Grid size={{ md: 2 }} alignSelf="center"> <Grid size={{ md: 2 }} alignSelf="center">
...@@ -85,8 +106,8 @@ function JumlahPerhitunganForm() { ...@@ -85,8 +106,8 @@ function JumlahPerhitunganForm() {
fullWidth fullWidth
size="large" size="large"
color="primary" color="primary"
// onClick={handleHitung} onClick={handleHitung}
// loading={hitung.isLoading} loading={isPending}
startIcon={<CalculateRounded />} startIcon={<CalculateRounded />}
> >
Hitung Hitung
......
...@@ -54,7 +54,7 @@ const PerhitunganPPhPasal21 = ({ kodeObjectPajak }: PPHDipotongProps) => { ...@@ -54,7 +54,7 @@ const PerhitunganPPhPasal21 = ({ kodeObjectPajak }: PPHDipotongProps) => {
<Field.Text <Field.Text
name="noDokLainnya" name="noDokLainnya"
label="Nomor Dokumen Lainnya" label="Nomor Dokumen Lainnya"
disabled={['9', ''].includes(fgFasilitas)} disabled={['9', ''].includes(fgFasilitas.value)}
sx={{ '& .MuiInputBase-root.Mui-disabled': { backgroundColor: '#f6f6f6' } }} sx={{ '& .MuiInputBase-root.Mui-disabled': { backgroundColor: '#f6f6f6' } }}
/> />
</Grid> </Grid>
......
...@@ -15,6 +15,12 @@ export const FG_PERHITUNGAN = { ...@@ -15,6 +15,12 @@ export const FG_PERHITUNGAN = {
MIXED: '2', MIXED: '2',
}; };
export const METODE_POTONG = {
GROSS: 'gross',
GROSS_UP: 'gross-up',
MIXED: 'mixed',
};
export const FG_PERHITUNGAN_TEXT = { export const FG_PERHITUNGAN_TEXT = {
[FG_PERHITUNGAN.GROSS]: 'Gross', [FG_PERHITUNGAN.GROSS]: 'Gross',
[FG_PERHITUNGAN.GROSS_UP]: 'Gross Up', [FG_PERHITUNGAN.GROSS_UP]: 'Gross Up',
......
...@@ -10,7 +10,7 @@ import type { ...@@ -10,7 +10,7 @@ import type {
import bulananApi from '../utils/api'; import bulananApi from '../utils/api';
export const transformFgStatusToFgSignStatus = (fgStatus: any) => { export const transformFgStatusToFgSignStatus = (fgStatus: any) => {
console.log('🚀 ~ transformFgStatusToFgSignStatus ~ fgStatus:', fgStatus); // console.log('🚀 ~ transformFgStatusToFgSignStatus ~ fgStatus:', fgStatus);
const splittedFgStatus = fgStatus?.split('-') || []; const splittedFgStatus = fgStatus?.split('-') || [];
if (splittedFgStatus.includes('SIGN') > 0) { if (splittedFgStatus.includes('SIGN') > 0) {
...@@ -65,36 +65,14 @@ export const transformSortModelToSortApiPayload = (transformedModel: any) => ({ ...@@ -65,36 +65,14 @@ export const transformSortModelToSortApiPayload = (transformedModel: any) => ({
sortingMethod: transformedModel.length > 0 ? transformedModel[0].sort : 'desc', sortingMethod: transformedModel.length > 0 ? transformedModel[0].sort : 'desc',
}); });
const normalisePropsGetDn = (params: TGetListDataTableDn) => ({ const normalisePropsGetBulanan = (params: TGetListDataTableDn) => ({
...params, ...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), fgSignStatus: transformFgStatusToFgSignStatus(params.fgStatus),
fgPdf: getFgStatusPdf(params.link, transformFgStatusToFgSignStatus(params.fgStatus)), fgPdf: getFgStatusPdf(params.link, transformFgStatusToFgSignStatus(params.fgStatus)),
fgLapor: params.fgLapor,
revNo: params.revNo, revNo: params.revNo,
thnPajak: params.tahunPajak, masaPajak: params.msPajak,
msPajak: params.masaPajak, pasalPPh: params.kdJnsPjk,
kdObjPjk: params.kodeObjekPajak,
noBupot: params.noBupot,
idDipotong: params.userId, 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 normalisPropsParmasGetDn = (params: any) => {
...@@ -119,7 +97,7 @@ const useGetBulanan = ({ params, ...props }: any) => { ...@@ -119,7 +97,7 @@ const useGetBulanan = ({ params, ...props }: any) => {
return { return {
...response, ...response,
// data: response.data.map((data) => normalisePropsGetDn(data)), data: response.data.map((data) => normalisePropsGetBulanan(data)),
}; };
}, },
initialData: { initialData: {
......
import { useMutation } from '@tanstack/react-query'; import { useMutation } from '@tanstack/react-query';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import dnApi from '../utils/api'; import bulananApi from '../utils/api';
import type { TPostDnRequest } from '../types/types'; import type { TPostDnRequest } from '../types/types';
const transformParams = ({ isPengganti = false, ...dnData }: any): TPostDnRequest => { const extractKapFromKodeObjekPajak = (kodeObjekPajak: string) =>
`4111${kodeObjekPajak.split('-')[0]}`;
const transformParams = ({ isPengganti = false, ...Data }: any): TPostDnRequest => {
const { const {
id, id,
idBupot, idBupot,
...@@ -17,85 +20,69 @@ const transformParams = ({ isPengganti = false, ...dnData }: any): TPostDnReques ...@@ -17,85 +20,69 @@ const transformParams = ({ isPengganti = false, ...dnData }: any): TPostDnReques
noDokLainnya, noDokLainnya,
kdObjPjk, kdObjPjk,
kdJnsPjk, kdJnsPjk,
statusPph,
jmlBruto, jmlBruto,
tarif, tarif,
pphDipotong, pphDipotong,
kap,
kjs,
revNo: initialRevNo, revNo: initialRevNo,
tglPemotongan, tglPemotongan,
namaDok,
nomorDok,
tglDok,
metodePembayaranBendahara,
nomorSP2D,
idTku, idTku,
email, fgGrossUp,
glAccount, komponen,
keterangan1, alamatDipotong,
keterangan2, foreignEmployee,
keterangan3, passportNo,
keterangan4, countryCode,
keterangan5, statusPtkp,
} = dnData; posisiJabatan,
} = Data;
const dokReferensi = [ console.log('🚀 ~ transformParams ~ Data:', Data);
{
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 revNo = isPengganti ? parseInt(initialRevNo || 0, 10) + 1 : parseInt(initialRevNo || 0, 10);
const [status, jmlPtkp] = statusPtkp.split('/');
const npwpLog = localStorage.getItem('npwp_log') ?? '';
return { return {
id: !isPengganti ? (id ?? null) : null, id: !isPengganti ? (id ?? null) : null,
idBupot: idBupot ?? '', idBupot: isPengganti ? (idBupot ?? '') : undefined,
noBupot: noBupot ?? '', noBupot: isPengganti ? (noBupot ?? '') : undefined,
npwpPemotong: npwpLog, npwpPemotong: idTku,
idTku: idTku ?? '', idTku: idTku ?? '',
masaPajak: msPajak ? dayjs(msPajak).format('MM') : '', masaPajak: msPajak ? dayjs(msPajak).format('MM') : '',
tahunPajak: thnPajak ? Number(dayjs(thnPajak).format('YYYY')) : 0, tahunPajak: thnPajak ? dayjs(thnPajak).format('YYYY') : '',
npwp: idDipotong ?? '', npwp: idDipotong ?? '',
nik: nitku ?? (idDipotong ? `${idDipotong}000000` : ''), nik: nitku ?? (idDipotong ? `${idDipotong}000000` : ''),
nama: namaDipotong ?? '', nama: namaDipotong ?? '',
revNo, revNo,
fgNpwpNik: 'true', // static fgNpwpNik: true,
fgJnsBupot: 'BPU', // static fgJnsBupot: 'A0',
dataDetilBpu: { alamat: alamatDipotong ?? '',
sertifikatInsentifDipotong: fgFasilitas ?? '9', dataDetilA0: {
nomorSertifikatInsentif: noDokLainnya ?? '', foreignEmployee: foreignEmployee ?? false,
passportNo: passportNo ?? '',
countryCode: countryCode ?? null,
statusPtkp: status ?? '',
jmlPtkp: jmlPtkp ?? '0',
posisiJabatan: posisiJabatan ?? '',
kodeObjekPajak: kdObjPjk ?? '', kodeObjekPajak: kdObjPjk ?? '',
pasalPPh: kdJnsPjk ?? '', pasalPPh: kdJnsPjk ?? '',
statusPPh: statusPph ?? '', penghasilanKotor: jmlBruto ?? 0,
dpp: jmlBruto ?? '', tarif: tarif ?? 0,
tarif: tarif ?? '', pphDipotong: pphDipotong ?? 0,
pphDipotong: pphDipotong ?? '', kap: extractKapFromKodeObjekPajak(kdObjPjk ?? ''),
kap: kap ?? '', kjs: '100',
kjs: kjs ?? '', sertifikatInsentifDipotong: fgFasilitas ?? '9',
dokReferensi, nomorSertifikatInsentif: noDokLainnya ?? '',
}, },
tglPemotongan: tglPemotongan ? dayjs(tglPemotongan).format('DDMMYYYY') : '', tglPemotongan: tglPemotongan ? dayjs(tglPemotongan).format('DDMMYYYY') : '',
email: email ?? '', fgGrossUp: fgGrossUp ?? 1,
glAccount: glAccount ?? '', komponen: komponen ?? [],
keterangan1: keterangan1 ?? '',
keterangan2: keterangan2 ?? '',
keterangan3: keterangan3 ?? '',
keterangan4: keterangan4 ?? '',
keterangan5: keterangan5 ?? '',
}; };
}; };
const useSaveBulanan = (props?: any) => const useSaveBulanan = (props?: any) =>
useMutation({ useMutation({
mutationKey: ['Save-Dn'], mutationKey: ['Save-Dn'],
mutationFn: (params: any) => dnApi.saveDn(transformParams(params)), mutationFn: (params: any) => bulananApi.saveBulanan(transformParams(params)),
...props, ...props,
}); });
......
...@@ -8,66 +8,147 @@ export type TBaseResponseAPI<T> = { ...@@ -8,66 +8,147 @@ export type TBaseResponseAPI<T> = {
total?: number; total?: number;
}; };
type TResponseData = {}; export interface BupotRecord {
// --- Kunci numerik dinamis ("1" sampai "53") ---
// [key: `${number}`]: number | undefined;
type TBaseResponseMetaPage = {
pageNum: number | null;
rowPerPage: number | null;
totalRow: number;
};
export type TGetListDataTableDn = {
id: number; id: number;
npwpPemotong: string; idBupot: string | null;
idTku: string; noBupot: string | null;
masaPajak: string; thnPajak: string;
tahunPajak: string; msPajak: string;
fgNpwpNik: string; namaPemotong: string | null;
npwp: string; fgIdDipotong: string; // "true" / "false" (string)
nik: string; idDipotong: string;
nama: string; namaDipotong: string;
sertifikatInsentifDipotong: string; tglPemotongan: string;
nomorSertifikatInsentif: string; kdJnsPjk: string;
kodeObjekPajak: string; namaTtd: string | null;
pasalPPh: string; nikNpwpTtd: string | null;
statusPPh: string; created_at: string;
dpp: string; updated_at: string;
errorMsg: string | null;
created: string;
updated: string;
email: string | null;
npwp16Pemotong: string;
nitkuPemotong: string;
npwp16Dipotong: string;
fgKirimEmail: number;
statusEmail: string | null;
messageid: string | null;
passphrasePenandatangan: string | null;
dcPenandatangan: string | null;
serialNumberPenandatangan: string | null;
userId: string;
foreignEmployee: string; // "true" / "false" (string)
passportNo: string;
countryCode: string | null;
statusPtkp: string;
jmlPtkp: number;
posisiJabatan: string;
kdObjPjk: string;
nmObjPjk: string | null;
pasalPPh: string | null;
bruto: string;
tarif: string; tarif: string;
pphDipotong: string; pphDipotong: string;
fgStatus: string;
fgFasilitas: string;
noDokLainnya: string;
kap: string; kap: string;
kjs: string; kjs: string;
tglpemotongan: string;
userId: string;
created_at: string;
updated_at: string;
created_by: string;
updated_by: string;
fgStatus: string;
internal_id: string; internal_id: string;
dokumen_referensi: { fgLapor: number;
dokReferensi: string;
nomorDokumen: string;
tanggal_Dokumen: string;
metodePembayaranBendahara: string;
nomorSP2D: string;
}[];
revNo: number; revNo: number;
noBupot: string | null; tglPembatalan: string | null;
idBupot: string | null; fgGrossUp: number;
npwpNikPenandatangan: string;
namaPenandatangan: string;
link: string | null; link: string | null;
errorMsg: string | null;
email: string | null;
glAccount: string; glAccount: string;
fgkirimemail: string; fgkirimemail: string;
tunjanganPPh: string;
pph21ditanggungperusahaan: string;
pph21ditanggungkaryawan: string;
alamat: string;
keterangan1: string | null;
keterangan2: string | null;
keterangan3: string | null;
keterangan4: string | null;
keterangan5: string | null;
}
type TBaseResponseMetaPage = {
pageNum: number | null;
rowPerPage: number | null;
totalRow: number;
};
export type TGetListDataTableDn = {
alamat: string;
bruto: string;
countryCode: string | null;
created?: string;
created_at: string;
dokReferensi: string;
email: string | null;
errorMsg: string | null;
fgFasilitas: string;
fgGrossUp: number;
fgIdDipotong: string;
fgKirimEmail: string;
fgLapor?: string;
fgPdf: string;
fgSignStatus: string | null;
fgStatus: string;
fgkirimemail: string;
foreignEmployee: string;
glAccount: string | null;
glName: string | null; glName: string | null;
id: number;
idBupot: string | null;
idDipotong: string;
internal_id: string;
jmlBruto?: string;
jmlPtkp: number;
kap: string;
kdJnsPjk: string;
kdObjPjk: string;
keterangan1: string | null; keterangan1: string | null;
keterangan2: string | null; keterangan2: string | null;
keterangan3: string | null; keterangan3: string | null;
keterangan4: string | null; keterangan4: string | null;
keterangan5: string | null; keterangan5: string | null;
fgLapor: string; kjs: string;
link: string | null;
masaPajak: string;
metodePembayaranBendahara: string;
msPajak: string;
namaDipotong?: string;
namaNegara: string | null;
namaPenandatangan: string | null;
nitkuPemotong: string;
noBupot: string | null;
noDokLainnya: string;
nomorDokumen: string;
nomorSP2D: string;
npwp16Dipotong: string;
npwp16Pemotong: string;
npwpNikPenandatangan: string | null;
npwpPemotong?: string;
passportNo: string;
posisiJabatan: string;
pph21ditanggungkaryawan: string;
pph21ditanggungperusahaan: string;
pphDipotong: string;
revNo: number;
statusPtkp: string;
tarif: string;
tglpemotongan: string;
thnPajak: string;
tunjanganPPh: string;
updated?: string;
updated_at: string;
userId: string;
}; };
export type TGetListDataTableDnResult = TGetListDataTableDn[]; export type TGetListDataTableDnResult = TGetListDataTableDn[];
...@@ -101,44 +182,41 @@ export type ActionItem = { ...@@ -101,44 +182,41 @@ export type ActionItem = {
}; };
export type TPostDnRequest = { export type TPostDnRequest = {
id: string | null; id?: number | null;
idBupot: string;
noBupot: string;
npwpPemotong: string; npwpPemotong: string;
idTku: string; idTku: string;
masaPajak: string; masaPajak: string;
tahunPajak: number; tahunPajak: string;
fgNpwpNik: boolean;
npwp: string; npwp: string;
nik: string; nik: string;
nama: string; nama: string;
revNo: number; fgJnsBupot: string;
fgNpwpNik: string; // static "true" alamat: string;
fgJnsBupot: string; // static "BPU" dataDetilA0: {
dataDetilBpu: { foreignEmployee: boolean;
sertifikatInsentifDipotong: string; passportNo: string;
nomorSertifikatInsentif: string; countryCode: string | null;
statusPtkp: string;
jmlPtkp: string;
posisiJabatan: string;
kodeObjekPajak: string; kodeObjekPajak: string;
pasalPPh: string; pasalPPh: string;
statusPPh: string; penghasilanKotor: number;
dpp: string; tarif: number;
tarif: string; pphDipotong: number;
pphDipotong: string;
kap: string; kap: string;
kjs: string; kjs: string;
dokReferensi: { sertifikatInsentifDipotong: string;
dokReferensi: string; nomorSertifikatInsentif: string;
nomorDokumen: string;
tanggal_Dokumen: string;
metodePembayaranBendahara: string;
nomorSP2D: string;
}[];
}; };
tglPemotongan: string; tglPemotongan: string;
email: string; noBupot?: string;
glAccount: string; idBupot?: string;
keterangan1: string; revNo: number;
keterangan2: string; fgGrossUp: number;
keterangan3: string; komponen: Array<{
keterangan4: string; kode: string;
keterangan5: string; value: string;
}>;
}; };
import type { import type {
BupotRecord,
TBaseResponseAPI, TBaseResponseAPI,
TGetListDataKOPDnResult, TGetListDataKOPDnResult,
TGetListDataTableDnResult, TGetListDataTableDnResult,
...@@ -44,7 +45,7 @@ bulananApi.saveBulanan = async (config: TPostDnRequest) => { ...@@ -44,7 +45,7 @@ bulananApi.saveBulanan = async (config: TPostDnRequest) => {
const { const {
data: { message, data }, data: { message, data },
status: statusCode, status: statusCode,
} = await bulananClient.post<TBaseResponseAPI<TPostDnRequest>>('/IF_TXR_028/a0', { } = await bulananClient.post<TBaseResponseAPI<BupotRecord>>('/IF_TXR_028/a0', {
...config, ...config,
}); });
if (statusCode !== 200) { if (statusCode !== 200) {
......
...@@ -9,9 +9,17 @@ import { DataGridPremium } from '@mui/x-data-grid-premium'; ...@@ -9,9 +9,17 @@ import { DataGridPremium } from '@mui/x-data-grid-premium';
import { useMemo, useState } from 'react'; 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 { 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';
const numberIDR = (number: string) =>
new Intl.NumberFormat('id-ID', {
currency: 'IDR',
// style: 'currency'
}).format(Number(number));
export type IColumnGrid = GridColDef & { export type IColumnGrid = GridColDef & {
field: field:
| 'fgStatus' | 'fgStatus'
...@@ -20,16 +28,17 @@ export type IColumnGrid = GridColDef & { ...@@ -20,16 +28,17 @@ export type IColumnGrid = GridColDef & {
| 'tahunPajak' | 'tahunPajak'
| 'kdObjPjk' | 'kdObjPjk'
| 'pasalPPh' | 'pasalPPh'
| 'npwp' | 'passportNo'
| 'nama' | 'countryCode'
| 'dpp' | 'npwp16Dipotong'
| 'namaDipotong'
| 'bruto'
| 'pph21ditanggungperusahaan'
| 'pphDipotong' | 'pphDipotong'
| 'idTku' | 'nitkuPemotong'
| 'dokReferensi' | 'created'
| 'nomorDokumen'
| 'created_by'
| 'created_at' | 'created_at'
| 'updated_by' | 'updated'
| 'updated_at' | 'updated_at'
| 'internal_id' | 'internal_id'
| 'keterangan1' | 'keterangan1'
...@@ -49,7 +58,7 @@ export function BulananListView() { ...@@ -49,7 +58,7 @@ export function BulananListView() {
}); });
const [filterModel, setFilterModel] = useState<GridFilterModel>({ items: [] }); const [filterModel, setFilterModel] = useState<GridFilterModel>({ items: [] });
const [sortModel, setSortModel] = useState<GridSortModel>([]); const [sortModel, setSortModel] = useState<GridSortModel>([]);
const [rowSelectionModel, setRowSelectionModel] = useState<any>([]); // const [rowSelectionModel, setRowSelectionModel] = useState<any>([]);
// const [rowSelectionModel, setRowSelectionModel] = // const [rowSelectionModel, setRowSelectionModel] =
// useState<GridRowSelectionModel>(new Set<GridRowId>()); // useState<GridRowSelectionModel>(new Set<GridRowId>());
...@@ -105,7 +114,7 @@ export function BulananListView() { ...@@ -105,7 +114,7 @@ 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, '123'); console.log({ data: data.data[0] });
// const handleChange = (event: React.SyntheticEvent, newValue: number) => { // const handleChange = (event: React.SyntheticEvent, newValue: number) => {
// setTabs1(newValue); // setTabs1(newValue);
...@@ -134,6 +143,8 @@ export function BulananListView() { ...@@ -134,6 +143,8 @@ export function BulananListView() {
{ {
field: 'fgStatus', field: 'fgStatus',
headerName: 'Status', headerName: 'Status',
headerAlign: 'center',
align: 'left',
width: 300, width: 300,
type: 'singleSelect', type: 'singleSelect',
valueOptions: statusOptions.map((opt) => opt.value), // filter dropdown pakai value valueOptions: statusOptions.map((opt) => opt.value), // filter dropdown pakai value
...@@ -142,27 +153,164 @@ export function BulananListView() { ...@@ -142,27 +153,164 @@ export function BulananListView() {
return option ? option.label : (params.value as string); return option ? option.label : (params.value as string);
}, },
}, },
{ field: 'noBupot', headerName: 'Nomor Bukti Pemotongan', width: 200 }, {
{ field: 'masaPajak', headerName: 'Masa Pajak', width: 150 }, field: 'noBupot',
{ field: 'tahunPajak', headerName: 'Tahun Pajak', width: 150 }, headerName: 'Nomor Bukti Pemotongan',
{ field: 'kdObjPjk', headerName: 'Kode Objek Pajak', width: 150 }, headerAlign: 'center',
{ field: 'npwp', headerName: 'Identitas', width: 150 }, align: 'left',
{ field: 'nama', headerName: 'Nama', width: 150 }, width: 200,
{ 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: 'masaPajak',
{ field: 'dokReferensi', headerName: 'Nama dokumen', width: 150 }, headerName: 'Masa - Tahun Pajak',
{ field: 'nomorDokumen', headerName: 'Nomor dokumen', width: 150 }, headerAlign: 'center',
{ field: 'created_by', headerName: 'Created', width: 150 }, align: 'center',
{ field: 'created_at', headerName: 'Created At', width: 200 },
{ field: 'updated_by', headerName: 'Updated', width: 150 }, minWidth: 150,
renderCell: (params) => `${params.row.masaPajak} - ${params.row.thnPajak}`,
},
{
field: 'kdObjPjk',
headerName: 'Kode Objek Pajak',
headerAlign: 'center',
align: 'center',
width: 150,
minWidth: 150,
},
{
field: 'pasalPPh',
headerName: 'Pasal PPh',
headerAlign: 'center',
align: 'center',
minWidth: 150,
},
{
field: 'passportNo',
headerName: 'Nomor Paspor',
headerAlign: 'center',
minWidth: 200,
align: 'left',
},
{
field: 'countryCode',
headerName: 'Negara',
headerAlign: 'center',
align: 'left',
minWidth: 200,
renderCell: (params) =>
isEmpty(params.value) ? '' : `${params.value} - ${params.row.namaNegara}`,
},
{
field: 'internal_id',
headerName: 'Referensi',
headerAlign: 'center',
align: 'left',
minWidth: 200,
},
{
field: 'npwp16Dipotong',
headerName: 'NPWP',
headerAlign: 'center',
align: 'left',
minWidth: 200,
renderCell: (params) => <Typography className="text-[14px] ">{params.value}</Typography>,
},
{
field: 'namaDipotong',
headerName: 'Nama',
headerAlign: 'center',
align: 'left',
minWidth: 200,
},
{
field: 'bruto',
headerName: 'Jumlah Bruto (Rp)',
headerAlign: 'center',
align: 'right',
minWidth: 200,
valueFormatter: (params) => {
if (params == null) {
return '0';
}
return numberIDR(params);
},
},
{
field: 'pph21ditanggungperusahaan',
headerName: 'Tunjangan PPh (Rp)',
headerAlign: 'center',
align: 'right',
minWidth: 200,
type: 'number',
valueFormatter: (params) => {
if (params == null) {
return '0';
}
return numberIDR(params);
},
},
{
field: 'pphDipotong',
headerName: 'Jumlah PPh Terutang (Rp)',
headerAlign: 'center',
align: 'right',
type: 'number',
valueFormatter: (params) => {
if (params == null) {
return '0';
}
return numberIDR(params);
},
},
{
field: 'nitkuPemotong',
headerName: 'NITKU Pemotong',
headerAlign: 'center',
align: 'left',
minWidth: 200,
},
{
field: 'created',
headerName: 'Created',
headerAlign: 'center',
minWidth: 150,
},
{ field: 'created_at', headerName: 'Created At', width: 150 },
{ field: 'updated', headerName: 'Updated', width: 150 },
{ field: 'updated_at', headerName: 'Update At', 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: 'keterangan1',
{ field: 'keterangan2', headerName: 'Keterangan 2', width: 200 }, headerName: 'Keterangan 1',
{ field: 'keterangan3', headerName: 'Keterangan 3', width: 150 }, minWidth: 200,
{ field: 'keterangan4', headerName: 'Keterangan 4', width: 150 }, flex: 1,
{ field: 'keterangan5', headerName: 'Keterangan 5', width: 150 }, },
{
field: 'keterangan2',
headerName: 'Keterangan 2',
minWidth: 200,
flex: 1,
},
{
field: 'keterangan3',
headerName: 'Keterangan 3',
minWidth: 200,
flex: 1,
},
{
field: 'keterangan4',
headerName: 'Keterangan 4',
minWidth: 200,
flex: 1,
},
{
field: 'keterangan5',
headerName: 'Keterangan 5',
minWidth: 200,
flex: 1,
},
]; ];
return ( return (
......
...@@ -5,7 +5,7 @@ import { ...@@ -5,7 +5,7 @@ import {
} from '@tanstack/react-query'; } from '@tanstack/react-query';
import type { AxiosError } from 'axios'; import type { AxiosError } from 'axios';
import { fetcherJava, endpoints } from 'src/lib/axios-ctas-box'; import { fetcherHitung, endpoints } from 'src/lib/axios-ctas-box';
import type { transformParamsBupotBulananProps } from './type'; import type { transformParamsBupotBulananProps } from './type';
import { checkPerhitunganBupot21 } from './helper'; import { checkPerhitunganBupot21 } from './helper';
...@@ -105,7 +105,7 @@ const { bulanan, harian, pasal17, pensiun, pesangon, tahunan, tahunanA2 } = endp ...@@ -105,7 +105,7 @@ const { bulanan, harian, pasal17, pensiun, pesangon, tahunan, tahunanA2 } = endp
const hitungBulananGross = async ( const hitungBulananGross = async (
params: transformParamsBupotBulananProps params: transformParamsBupotBulananProps
): Promise<ApiResponseBulanan> => { ): Promise<ApiResponseBulanan> => {
const response = await fetcherJava<ApiResponse<ApiResponseBulanan>>([ const response = await fetcherHitung<ApiResponse<ApiResponseBulanan>>([
bulanan, bulanan,
{ {
method: 'POST', method: 'POST',
...@@ -119,7 +119,7 @@ const hitungBulananGross = async ( ...@@ -119,7 +119,7 @@ const hitungBulananGross = async (
const hitungBulananGrossUp = async ( const hitungBulananGrossUp = async (
params: transformParamsBupotBulananProps params: transformParamsBupotBulananProps
): Promise<ApiResponseBulanan> => { ): Promise<ApiResponseBulanan> => {
const response = await fetcherJava<ApiResponse<ApiResponseBulanan>>([ const response = await fetcherHitung<ApiResponse<ApiResponseBulanan>>([
bulanan, bulanan,
{ {
method: 'POST', method: 'POST',
......
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