Commit 25204a36 authored by Rais Aryaguna's avatar Rais Aryaguna

fix:

- validation form
- call api create draft and upload
- flow edit and pengganti
parent b7ff97de
......@@ -9,7 +9,7 @@ interface ApiResponse<T> {
data: T;
}
interface KodeNegara {
export interface KodeNegara {
kode: string;
nama: string;
}
......
......@@ -55,7 +55,7 @@ const DialogPenandatangan: React.FC<DialogPenandatanganProps> = ({
}) => {
const [isOpenDialogProgressBar, setIsOpenDialogProgressBar] = useState(false);
const [isCheckedAgreement, setIsCheckedAgreement] = useState<boolean>(false);
const signer = useAppSelector((state: any) => state.user.data.signer);
const signer = useAppSelector((state) => state.user.data.signer);
const queryClient = useQueryClient();
const methods = useForm({
......
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 { useEffect } 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;
kodeNegetaOptions: {
label: string;
value: string;
}[];
};
const Identitas = ({ isPengganti }: IdentitasProps) => {
const Identitas = ({ isPengganti, kodeNegetaOptions }: IdentitasProps) => {
// const { dnId } = useParams();
const { setValue, watch } = useFormContext();
const tanggalPemotongan = watch('tglPemotongan');
const fgKaryawanAsing = watch('fgKaryawanAsing');
const [jumlahKeterangan, setJumlahKeterangan] = useState<number>(0);
const maxKeterangan = 5;
// const [jumlahKeterangan, setJumlahKeterangan] = useState<number>(0);
// const maxKeterangan = 5;
const handleTambah = () => {
if (jumlahKeterangan < maxKeterangan) {
setJumlahKeterangan(jumlahKeterangan + 1);
}
};
// const handleTambah = () => {
// if (jumlahKeterangan < maxKeterangan) {
// setJumlahKeterangan(jumlahKeterangan + 1);
// }
// };
const handleHapus = () => {
if (jumlahKeterangan > 0) {
const newCount = jumlahKeterangan - 1;
setJumlahKeterangan(newCount);
setValue(`keterangan${newCount + 1}`, null);
}
};
// 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', '');
if (!isPengganti) {
if (tanggalPemotongan) {
const date = dayjs(tanggalPemotongan);
setValue('tahunPajak', date.format('YYYY'));
setValue('masaPajak', date.format('MM'));
} else {
setValue('tahunPajak', '');
setValue('masaPajak', '');
}
}
}, [tanggalPemotongan, setValue]);
const { kodeNegara } = useKodeNegara();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [tanggalPemotongan, !isPengganti]);
return (
<>
......@@ -62,54 +64,68 @@ const Identitas = ({ isPengganti }: IdentitasProps) => {
</Grid>
<Grid size={{ md: 3 }}>
<Field.DatePicker
name="thnPajak"
name="tahunPajak"
label="Tahun Pajak"
view="year"
format="YYYY"
readOnly
disabled={isPengganti}
/>
</Grid>
<Grid size={{ md: 3 }}>
<Field.DatePicker name="msPajak" label="Masa Pajak" view="month" format="MM" readOnly />
<Field.DatePicker
name="masaPajak"
label="Masa Pajak"
view="month"
format="MM"
readOnly
disabled={isPengganti}
/>
</Grid>
{/* NPWP dengan onChange langsung */}
<Grid size={{ md: 6 }}>
<Field.Text
name="idDipotong"
name="npwp"
label="NPWP"
onChange={(e) => {
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('npwp', value, { shouldValidate: true, shouldDirty: true });
setValue('nitku', value.length === 16 ? value + '000000' : value, {
shouldValidate: true,
shouldDirty: true,
});
}}
disabled={isPengganti}
/>
</Grid>
<Grid size={{ md: 6 }}>
<Field.Text name="namaDipotong" label="Nama" />
<Field.Text name="namaDipotong" label="Nama" disabled={isPengganti} />
</Grid>
<Grid size={{ md: 12 }}>
<Field.Text name="alamatDipotong" label="Alamat" />
</Grid>
<Grid size={{ md: 6 }}>
<Field.Text name="email" label="Email (optional)" />
<Field.Text name="email" label="Email (optional)" disabled={isPengganti} />
</Grid>
<Grid size={{ md: 6 }}>
<Field.Text name="posisiJabatan" label="Jabatan" />
<Field.Text name="posisiJabatan" label="Jabatan" disabled={isPengganti} />
</Grid>
<Grid size={{ md: 3 }} alignSelf="center">
<Field.Checkbox name="fgKaryawanAsing" label="Status Karyawan Asing" />
<Field.Checkbox
name="fgKaryawanAsing"
label="Status Karyawan Asing"
disabled={isPengganti}
/>
</Grid>
<Grid size={{ md: 3 }}>
<Field.Autocomplete
name="kodeNegara"
label="Negara"
options={kodeNegara.map((val) => ({ label: val.nama, value: val.kode }))}
options={kodeNegetaOptions}
readOnly={!fgKaryawanAsing}
disabled={isPengganti}
/>
</Grid>
<Grid size={{ md: 6 }}>
......@@ -121,12 +137,13 @@ const Identitas = ({ isPengganti }: IdentitasProps) => {
readOnly: !fgKaryawanAsing,
},
}}
disabled={isPengganti}
/>
</Grid>
</Grid>
{/* Tambah / Hapus Keterangan */}
<Box sx={{ display: 'flex', gap: 2, mb: 3 }}>
{/* <Box sx={{ display: 'flex', gap: 2, mb: 3 }}>
<Box
sx={{
borderRadius: '18px',
......@@ -163,7 +180,7 @@ const Identitas = ({ isPengganti }: IdentitasProps) => {
/>
</Grid>
))}
</Box>
</Box> */}
</>
);
};
......
......@@ -2,7 +2,6 @@ import { CalculateRounded } from '@mui/icons-material';
import { LoadingButton } from '@mui/lab';
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 { RHFNumeric } from 'src/components/hook-form/rhf-numeric';
......@@ -12,9 +11,6 @@ import {
FG_PERHITUNGAN,
FG_PERHITUNGAN_TEXT,
METODE_POTONG,
PTKP,
PTKP_TEXT,
PTKP_TITLE,
} from '../../constant';
const fgPerhitunganOptions = Object.values(FG_PERHITUNGAN).map((value) => ({
......@@ -22,17 +18,16 @@ const fgPerhitunganOptions = Object.values(FG_PERHITUNGAN).map((value) => ({
label: FG_PERHITUNGAN_TEXT[value],
}));
function JumlahPerhitunganForm() {
function JumlahPerhitunganForm({
ptkpOptions,
}: {
ptkpOptions: {
value: string;
label: string;
}[];
}) {
const { watch, getValues, setValue } = useFormContext();
const ptkpOptions = useMemo(
() =>
Object.entries(PTKP)
.map(([key, value]) => ({ value, label: PTKP_TEXT[value] }))
.filter((option) => !option.value.includes(PTKP_TITLE.HB)),
[]
);
const { mutate, isPending } = useHitungBulanan({
onSuccess: (data) => {
console.log('✅ Berhasil hitung PPh21:', data);
......
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;
}[];
fgFasilitasOptions: {
value: string;
label: string;
}[];
};
const PerhitunganPPhPasal21 = ({ kodeObjectPajak }: PPHDipotongProps) => {
const PerhitunganPPhPasal21 = ({ kodeObjectPajak, fgFasilitasOptions }: 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 */}
......
......@@ -12,9 +12,9 @@ const transformParams = ({ isPengganti = false, ...Data }: any): TPostBulananReq
id,
idBupot,
noBupot,
msPajak,
thnPajak,
idDipotong,
masaPajak,
tahunPajak,
npwp,
nitku,
namaDipotong,
fgFasilitas,
......@@ -36,21 +36,21 @@ const transformParams = ({ isPengganti = false, ...Data }: any): TPostBulananReq
posisiJabatan,
} = Data;
console.log('🚀 ~ transformParams ~ Data:', Data);
const revNo = isPengganti ? parseInt(initialRevNo || 0, 10) + 1 : parseInt(initialRevNo || 0, 10);
const [status, jmlPtkp] = statusPtkp.split('/');
const npwpLog = localStorage.getItem('npwp_log') ?? '';
return {
id: !isPengganti ? (id ?? null) : null,
idBupot: isPengganti ? (idBupot ?? '') : undefined,
noBupot: isPengganti ? (noBupot ?? '') : undefined,
npwpPemotong: idTku,
idBupot,
noBupot,
npwpPemotong: npwpLog,
idTku: idTku ?? '',
masaPajak: msPajak ? dayjs(msPajak).format('MM') : '',
tahunPajak: thnPajak ? dayjs(thnPajak).format('YYYY') : '',
npwp: idDipotong ?? '',
nik: nitku ?? (idDipotong ? `${idDipotong}000000` : ''),
masaPajak,
tahunPajak,
npwp: npwp ?? '',
nik: nitku ?? (npwp ? `${npwp}000000` : ''),
nama: namaDipotong ?? '',
revNo,
fgNpwpNik: true,
......
......@@ -8,6 +8,14 @@ export type TBaseResponseAPI<T> = {
total?: number;
};
export type TBaseResponseCreateAPI<T> = {
code: number;
data?: T;
message: string;
status: string;
time: Date;
};
export interface BupotRecord {
// --- Kunci numerik dinamis ("1" sampai "53") ---
// [key: `${number}`]: number | undefined;
......
import { fetcher } from 'src/lib/axios';
import { fetcher } from 'src/lib/axios-ctas-box';
import type {
BupotRecord,
TBaseResponseAPI,
......@@ -46,7 +46,7 @@ bulananApi.getKodeObjekPajak = async (params?: Record<string, any>) => {
};
bulananApi.save = async (config: TPostBulananRequest) => {
const response = await fetcher<TBaseResponseAPI<BupotRecord>>([
const response = await fetcher<TBaseResponseAPI<BupotRecord[]>>([
'/IF_TXR_028/a0',
{
method: 'POST',
......
......@@ -4,8 +4,11 @@ import Divider from '@mui/material/Divider';
import Grid from '@mui/material/Grid';
import Stack from '@mui/material/Stack';
import dayjs from 'dayjs';
import { enqueueSnackbar } from 'notistack';
import { Suspense, useEffect, useMemo, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useNavigate, useParams } from 'react-router';
import { useKodeNegara } from 'src/actions/master-data';
import { CustomBreadcrumbs } from 'src/components/custom-breadcrumbs';
import { Field } from 'src/components/hook-form';
import { DashboardContent } from 'src/layouts/dashboard';
......@@ -14,6 +17,7 @@ import Agreement from 'src/shared/components/agreement/Agreement';
import HeadingRekam from 'src/shared/components/HeadingRekam';
import FormSkeleton from 'src/shared/skeletons/FormSkeleton';
import z from 'zod';
import DialogPenandatangan from '../components/DialogPenandatangan';
import Identitas from '../components/rekam/Identitas';
import JumlahPerhitunganForm from '../components/rekam/JumlahPerhitunganForm';
import PanduanDnRekam from '../components/rekam/PanduanDnRekam';
......@@ -23,27 +27,34 @@ import {
FG_FASILITAS_PPH_21_TEXT,
KODE_OBJEK_PAJAK,
KODE_OBJEK_PAJAK_TEXT,
PTKP,
PTKP_TEXT,
PTKP_TITLE,
} from '../constant';
import useGetBulanan from '../hooks/useGetBulanan';
import useSaveBulanan from '../hooks/useSaveBulanan';
import DialogPenandatangan from '../components/DialogPenandatangan';
import useUploadBulanan from '../hooks/useUploadeBulanan';
import { useNavigate, useParams } from 'react-router';
import { enqueueSnackbar } from 'notistack';
import useGetBulanan from '../hooks/useGetBulanan';
const bulananSchema = z
.object({
idBupot: z.string().optional(),
noBupot: z.string().optional(),
revNo: z.number().optional(),
tahunPajak: z.string().min(1, 'Tahun Pajak wajib diisi'),
masaPajak: z.string().min(1, 'Masa Pajak wajib diisi'),
feature: z.string().optional(),
idDipotong: z.string().optional(),
npwp: z.string().min(1, 'NPWP wajib diisi'),
nitku: z.string().length(22, 'NITKU harus 22 digit'),
namaDipotong: z.string().min(1, 'Nama wajib diisi'),
alamatDipotong: z.string().min(1, 'Alamat wajib diisi'),
posisiJabatan: z.string().min(1, 'Jabatan wajib diisi'),
alamat: z.string().nullable().optional(),
fgKaryawanAsing: z.boolean().optional(),
kodeNegara: z.string().optional(),
kodeNegara: z
.object({
label: z.string(),
value: z.string(),
})
.optional(),
passport: z.string().optional(),
kdObjPjk: z
.object({
......@@ -172,7 +183,7 @@ export const BulananRekamView = () => {
const { id, type } = useParams<{ id?: string; type?: 'ubah' | 'pengganti' | 'new' }>();
const navigate = useNavigate();
const { mutate: saveBulanan, isPending: isSaving } = useSaveBulanan({
const { mutateAsync: saveBulanan, isPending: isSaving } = useSaveBulanan({
onSuccess: () => enqueueSnackbar('Data berhasil disimpan', { variant: 'success' }),
});
const { mutate: uploadBulanan, isPending: isUpload } = useUploadBulanan();
......@@ -195,13 +206,52 @@ export const BulananRekamView = () => {
[]
);
const { kodeNegara } = useKodeNegara();
const kodeNegetaOptions = useMemo(
() => kodeNegara.map((val) => ({ label: val.nama, value: val.kode })),
[kodeNegara]
);
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],
})),
[]
);
const ptkpOptions = useMemo(
() =>
Object.entries(PTKP)
.map(([key, value]) => ({ value, label: PTKP_TEXT[value] }))
.filter((option) => !option.value.includes(PTKP_TITLE.HB)),
[]
);
const MockNitku = [
{
value: '1091031210912281000000',
label: '1091031210912281000000',
},
{
value: '1091031210912281000001',
label: '1091031210912281000001',
},
];
const { data: existingBulanan, isLoading: isLoadingBulanan } = useGetBulanan({
params: {
page: 1,
limit: 1,
id,
},
enabled: !!id,
enabled: !!id && (isEdit || isPengganti),
});
type BpuFormData = z.infer<typeof bulananSchema>;
......@@ -211,12 +261,15 @@ export const BulananRekamView = () => {
const defaultValues = {
tglPemotongan: dayjs().format('YYYY-MM-DD'),
tahunPajak: dayjs().format('YYYY'),
masaPajak: dayjs().format('YYYY-MM'),
masaPajak: dayjs().format('MM'),
npwp: '',
nitku: '',
namaDipotong: '',
fgKaryawanAsing: false,
kodeNegara: '',
kodeNegara: {
value: '',
label: '',
},
passport: '',
posisiJabatan: '',
kdObjPjk: {
......@@ -251,12 +304,47 @@ export const BulananRekamView = () => {
defaultValues,
});
console.log('🚀 ~ BulananRekamView:', {
methods: methods.getValues(),
error: methods.formState.errors,
});
useEffect(() => {
if ((isEdit || isPengganti) && existingBulanan && !isLoadingBulanan) {
if ((isEdit || isPengganti) && existingBulanan.data.length !== 0) {
const dataResDetail = existingBulanan.data[0];
const normalized = {
...existingBulanan,
...dataResDetail,
tglPemotongan: dataResDetail.tglpemotongan,
tahunPajak: dataResDetail.thnPajak,
masaPajak: dataResDetail.masaPajak,
npwp: dataResDetail.npwp16Dipotong,
nitku: `${dataResDetail.npwp16Dipotong}000000`,
alamatDipotong: dataResDetail.alamat,
email: dataResDetail.email || '',
fgKaryawanAsing: !!dataResDetail.countryCode,
kodeNegara: kodeNegetaOptions.filter(
(val) => val.value === dataResDetail.countryCode
)[0] || {
value: '',
label: '',
},
passport: dataResDetail.passportNo || '',
kdObjPjk: dataListKOP.filter((val) => val.value === dataResDetail.kdObjPjk)[0],
fgFasilitas: fgFasilitasOptions.filter((val) => val.value === dataResDetail.fgFasilitas)[0],
ptkp: ptkpOptions.filter(
(val) => val.value === `${dataResDetail.statusPtkp}/${dataResDetail.jmlPtkp}`
)[0],
phBruto: dataResDetail.bruto,
pph21: dataResDetail.pphDipotong,
idTku: MockNitku.filter((val) => val.value === dataResDetail.nitkuPemotong)[0],
};
if (isPengganti) {
normalized['idBupot'] = dataResDetail.idBupot || '';
normalized['idBupot'] = dataResDetail.noBupot || '';
normalized['revNo'] = dataResDetail.revNo || 0;
}
methods.reset(normalized as any);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
......@@ -267,10 +355,8 @@ export const BulananRekamView = () => {
const transformedData = {
...data,
id: isEdit || isPengganti ? data.id : undefined,
msPajak: data.masaPajak,
thnPajak: data.tahunPajak,
passportNo: data.passport || '',
countryCode: data.kodeNegara || null,
countryCode: data.kodeNegara?.value || null,
statusPtkp: data.ptkp?.value || '',
kdObjPjk: data.kdObjPjk.value,
kdJnsPjk: 'PPh Pasal 21', // Sesuaikan dengan konstanta Anda
......@@ -282,16 +368,19 @@ export const BulananRekamView = () => {
fgGrossUp: parseInt(data.fgPerhitungan || '1', 10),
idTku: data.idTku.value,
komponen: data.jumlahPenghasilan || [],
isPengganti,
};
await saveBulanan(transformedData);
const response = await saveBulanan(transformedData);
return response;
};
const handleUploud = async (data: BpuFormData) => {
try {
const response = await handleDraft(data);
uploadBulanan({
id: response?.id ?? '',
id: `${response[0].id}`,
});
enqueueSnackbar('Berhasil Menyimpan Data', { variant: 'success' });
} catch (error: any) {
......@@ -307,8 +396,7 @@ export const BulananRekamView = () => {
const SubmitRekam = async (data: BpuFormData) => {
try {
const respon = await handleDraft(data);
console.log({ respon });
await handleDraft(data);
enqueueSnackbar(
isEdit
? 'Data berhasil diperbarui'
......@@ -325,15 +413,6 @@ export const BulananRekamView = () => {
}
};
const MockNitku = [
{
nama: '1091031210912281000000',
},
{
nama: '1091031210912281000001',
},
];
return (
<>
<DashboardContent>
......@@ -356,20 +435,19 @@ export const BulananRekamView = () => {
<form onSubmit={methods.handleSubmit(SubmitRekam)}>
<FormProvider {...methods}>
<Suspense fallback={<FormSkeleton />}>
<Identitas />
<Identitas isPengganti={isPengganti} kodeNegetaOptions={kodeNegetaOptions} />
<Suspense fallback={<FormSkeleton />}>
<PerhitunganPPhPasal21 kodeObjectPajak={dataListKOP} />
<PerhitunganPPhPasal21
kodeObjectPajak={dataListKOP}
fgFasilitasOptions={fgFasilitasOptions}
/>
</Suspense>
<JumlahPerhitunganForm />
<JumlahPerhitunganForm ptkpOptions={ptkpOptions} />
<Grid size={{ md: 12 }}>
<Field.Autocomplete
name="idTku"
label="NITKU Pemotong"
options={MockNitku.map((a) => ({ value: a.nama, label: a.nama }))}
/>
<Field.Autocomplete name="idTku" label="NITKU Pemotong" options={MockNitku} />
</Grid>
<Divider />
......
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