Commit 5dae0e7c authored by Rais Aryaguna's avatar Rais Aryaguna

fix: tahunan a1

parent 9a37352f
......@@ -2,7 +2,7 @@
// CONSTANTS & CONFIGURATION
// ============================================
import { FG_PERHITUNGAN } from "../../constant";
import { FG_PERHITUNGAN } from '../../constant';
// Field Names Mapping
const FORM_FIELDS = {
......@@ -15,13 +15,13 @@ const FORM_FIELDS = {
NATURA: 'rincian6',
TANTIEM_BONUS: 'rincian7',
JUMLAH_BRUTO: 'rincian8',
// Pengurangan
BIAYA_JABATAN: 'rincian9',
IURAN_PENSIUN: 'rincian10',
ZAKAT: 'rincian11',
JUMLAH_PENGURANGAN: 'rincian12',
// Penghitungan PPh Pasal 21
PENGHASILAN_NETO: 'rincian13',
PENGHASILAN_NETO_SEBELUMNYA: 'rincian14',
......@@ -87,7 +87,6 @@ const CALCULATED_FIELDS = new Set([
FORM_FIELDS.BIAYA_JABATAN,
FORM_FIELDS.JUMLAH_PENGURANGAN,
FORM_FIELDS.PENGHASILAN_NETO,
FORM_FIELDS.PENGHASILAN_NETO_SEBELUMNYA,
FORM_FIELDS.JUMLAH_PENGHASILAN_NETO,
FORM_FIELDS.PTKP,
FORM_FIELDS.PENGHASILAN_KENA_PAJAK,
......@@ -98,27 +97,28 @@ const CALCULATED_FIELDS = new Set([
FORM_FIELDS.PPH_KURANG_LEBIH,
]);
// ============================================
// HELPER FUNCTIONS
// ============================================
const isFieldReadOnly = (fieldName: string, msPjkAwal: number, isMetodePemotonganSeTahun: any ,fgPerhitungan: string) => {
const isFieldReadOnly = (
fieldName: string,
msPjkAwal: number,
isMetodePemotonganSeTahun: any,
fgPerhitungan: string
) => {
// Calculated fields are always readonly
if (CALCULATED_FIELDS.has(fieldName)) {
return true;
}
// Special case: Tunjangan PPh
if (fieldName === FORM_FIELDS.TUNJANGAN_PPH) {
console.log("🚀 ~ isFieldReadOnly:",{ msPjkAwal, isMetodePemotonganSeTahun});
return fgPerhitungan === FG_PERHITUNGAN.GROSS_UP;
}
if (fieldName === FORM_FIELDS.PENGHASILAN_NETO_SEBELUMNYA) {
return msPjkAwal >= 2 && isMetodePemotonganSeTahun !== '1'
return msPjkAwal === 1 || isMetodePemotonganSeTahun === '1'
}
// If PPh21 is active, all input fields are readonly
return false;
......@@ -129,13 +129,13 @@ const getFieldNameByIndex = (index: any) => {
return fieldNames[index] || `rincian${index + 1}`;
};
const perhitunganA1List = Object.values(FORM_FIELDS).map(values=>({[values]:0}))
const perhitunganA1List = Object.values(FORM_FIELDS).map((values) => ({ [values]: 0 }));
export {
FORM_FIELDS,
FORM_SECTIONS,
CALCULATED_FIELDS,
isFieldReadOnly,
getFieldNameByIndex,
perhitunganA1List,
}
FORM_FIELDS,
FORM_SECTIONS,
CALCULATED_FIELDS,
isFieldReadOnly,
getFieldNameByIndex,
perhitunganA1List,
};
......@@ -5,7 +5,7 @@ import { enqueueSnackbar } from 'notistack';
import { useEffect, useState } from 'react';
import DialogUmum from 'src/shared/components/dialog/DialogUmum';
import { useAppSelector } from 'src/store';
import { useCetakBulanan } from '../../../cetakpdf';
import { useCetakTahunanA1 } from '../../../cetakpdf';
interface ModalCetakPdfDnProps {
payload?: Record<string, any>;
......@@ -13,12 +13,12 @@ interface ModalCetakPdfDnProps {
onClose: () => void;
}
const ModalCetakPdfBulanan: React.FC<ModalCetakPdfDnProps> = ({ payload, isOpen, onClose }) => {
const ModalCetakPdf: React.FC<ModalCetakPdfDnProps> = ({ payload, isOpen, onClose }) => {
const [pdfUrl, setPdfUrl] = useState<string | null>(null);
const [loading, setLoading] = useState<boolean>(false);
const AppSelector = useAppSelector((selector) => selector.user.data);
const { mutateAsync } = useCetakBulanan({
const { mutateAsync } = useCetakTahunanA1({
onError: (error: any) => {
enqueueSnackbar(error?.message || 'Gagal memuat PDF', { variant: 'error' });
setLoading(false);
......@@ -66,7 +66,7 @@ const ModalCetakPdfBulanan: React.FC<ModalCetakPdfDnProps> = ({ payload, isOpen,
maxWidth="lg"
isOpen={isOpen}
onClose={onClose}
title="Detail Bupot Unifikasi (PDF)"
title="Detail Bupot Tahunan A1 (PDF)"
>
<DialogContent classes={{ root: 'p-16 sm:p-24' }}>
{loading && (
......@@ -84,7 +84,7 @@ const ModalCetakPdfBulanan: React.FC<ModalCetakPdfDnProps> = ({ payload, isOpen,
border: 'none',
borderRadius: 8,
}}
title="Preview PDF Bupot"
title="Preview PDF Tahunan A1"
/>
)}
......@@ -98,4 +98,4 @@ const ModalCetakPdfBulanan: React.FC<ModalCetakPdfDnProps> = ({ payload, isOpen,
);
};
export default ModalCetakPdfBulanan;
export default ModalCetakPdf;
......@@ -198,7 +198,6 @@ export default function PerhitunganA1Container() {
const msPjkAwal = dayjs(watch('masaPajakAwal')).get('month') + 1;
const isMetodePemotonganSeTahun = watch('metodePemotongan');
useRincianCalculations();
const { mutate, isPending } = hitungTahunanA1({
......@@ -206,7 +205,7 @@ export default function PerhitunganA1Container() {
console.log('✅ Berhasil hitung Tahunan A1:', data);
// TODO: Update form values dengan data dari response
Object.keys(data).forEach((key) => {
setValue(key, data[key]);
setValue(key, `${data[key]}`);
});
},
onError: (error) => {
......@@ -241,7 +240,12 @@ export default function PerhitunganA1Container() {
section.fields.forEach((label, localIndex) => {
const fieldNumber = section.startNumber + localIndex;
const fieldName = getFieldNameByIndex(globalIndex);
const readOnly = isFieldReadOnly(fieldName, msPjkAwal, isMetodePemotonganSeTahun ,fgPerhitungan);
const readOnly = isFieldReadOnly(
fieldName,
msPjkAwal,
isMetodePemotonganSeTahun,
fgPerhitungan
);
// Special case: Tunjangan PPh dengan checkbox Gross Up
if (fieldName === FORM_FIELDS.TUNJANGAN_PPH) {
......@@ -300,8 +304,14 @@ export default function PerhitunganA1Container() {
});
return result;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [fgPerhitungan, isPending]);
}, [
fgPerhitungan,
handleGrossUpChange,
handleHitung,
isMetodePemotonganSeTahun,
isPending,
msPjkAwal,
]);
return <PerhitunganA1Builder listInputs={listInputs} labelCols={9} inputCols={3} />;
}
......@@ -66,6 +66,14 @@ const RincianPenghasilan = ({
name="metodePemotongan"
label="Jenis Pemotongan"
options={setahunOptions}
onChange={(val) => {
setValue('metodePemotongan', val.target.value)
if (val.target.value === '1') {
setValue('masaPajakAwal', '01')
setValue('masaPajakAkhir', '12')
setValue('noBupotSebelumnya', '')
}
}}
slotProps={{
radio: {
slotProps: {
......@@ -109,7 +117,7 @@ const RincianPenghasilan = ({
format="MM"
openTo="month"
maxDate={dayjs(masaPajakAkhir)}
readOnly={isMetodePemotonganSeTahun || isPengganti}
// readOnly={isMetodePemotonganSeTahun || isPengganti}
/>
</Grid>
<Grid size={{ md: 1 }}>
......@@ -123,11 +131,11 @@ const RincianPenghasilan = ({
<Field.DatePicker
name="masaPajakAkhir"
label="Masa Pajak Akhir"
view="month"
view='month'
format="MM"
openTo="month"
minDate={dayjs(masaPajakAwal)}
readOnly={isMetodePemotonganSeTahun || isPengganti}
// readOnly={isMetodePemotonganSeTahun || isPengganti}
/>
</Grid>
......
......@@ -11,7 +11,7 @@ const useDeleteTahunanA1 = (
) =>
useMutation<any, Error, TPortBulananRequest, unknown>({
mutationKey: queryKey.tahunanA1.upload,
mutationFn: (params: TPortBulananRequest) => tahunanA1Api.delete(params),
mutationFn: (params: TPortBulananRequest) => tahunanA1Api.upload(params),
...props,
});
......
......@@ -55,7 +55,7 @@ const transformParams = ({ isPengganti = false, ...Data }: any): TPostTahunanA1R
dataDetilBupotA1: {
fgKaryawanAsing: Data.fgKaryawanAsing,
passport: isEmpty(Data.passport) ? null : Data.passport,
kdNegara: !Data.fgKaryawanAsing ? 'IDN' : Data.kodeNegara,
kdNegara: !Data.fgKaryawanAsing ? 'IDN' : Data.kodeNegara.value,
posisiJabatan: Data.namaJabatan,
gajiPensiun: Data.rincian1,
tunjanganPPh: Data.rincian2,
......@@ -69,7 +69,7 @@ const transformParams = ({ isPengganti = false, ...Data }: any): TPostTahunanA1R
iuranPensiun: Data.rincian10,
zakat: Data.rincian11,
fgFasilitas: Data.fgFasilitas === FG_FASILITAS_PPH_21.DTP ? '11' : Data.fgFasilitas,
noDokFasilitas: Data.noDokFasilitas,
noDokFasilitas: Data.noDokLainnya,
},
totalPenghasilanBruto: Data.rincian8,
totalPengurang: Data.rincian12,
......
......@@ -139,16 +139,16 @@ const columns: IColumnGrid[] = [
minWidth: 200,
align: 'left',
},
{
field: 'kdNegara',
headerName: 'Negara',
headerAlign: 'center',
align: 'left',
minWidth: 200,
renderCell: (params) =>
isEmpty(params.value) ? '' : `${params.value} - ${params.row.namaNegara}`,
},
// {
// field: 'kdNegara',
// headerName: 'Negara',
// headerAlign: 'center',
// align: 'left',
// minWidth: 200,
// renderCell: (params) =>
// isEmpty(params.value) ? '' : `${params.value} - ${params.row.namaNegara}`,
// },
{
field: 'internal_id',
headerName: 'Referensi',
......
......@@ -16,6 +16,8 @@ import { paths } from 'src/routes/paths';
import {
FG_FASILITAS_PPH_21,
FG_FASILITAS_PPH_21_TEXT,
KELAMIN,
KELAMIN_TEXT,
KODE_OBJEK_PAJAK,
KODE_OBJEK_PAJAK_TEXT,
MockNitku,
......@@ -23,6 +25,7 @@ import {
PTKP_TEXT,
PTKP_TITLE,
SETAHUN,
SETAHUN_TEXT,
} from 'src/sections/bupot-21-26/constant';
import Agreement from 'src/shared/components/agreement/Agreement';
import HeadingRekam from 'src/shared/components/HeadingRekam';
......@@ -42,7 +45,7 @@ const TahunanA1Schema = z
id: z.number().optional(),
idBupot: z.string().optional(),
noBupot: z.string().optional(),
revNo: z.number().optional(),
revNo: z.string().optional(),
internal_id: z.string().optional(),
npwp: z
......@@ -81,7 +84,7 @@ const TahunanA1Schema = z
})
.optional(),
passport: z.string().optional(),
metodePemotongan: z.string().min(1, 'Jenis Pemotongan wajib diisi'),
metodePemotongan: z.string().min(1, 'Jenis wajib diisi'),
noBupotSebelumnya: z.string().optional(),
tglPemotongan: z.string().min(1, 'Tanggal Pemotongan wajib diisi'),
tahunPajak: z.string().min(1, 'Tahun Pajak wajib diisi'),
......@@ -103,14 +106,7 @@ const TahunanA1Schema = z
},
{ message: 'Masa Pajak Awal tidak boleh diatas masa berjalan' }
),
masaPajakAkhir: z.string('Masa Pajak Akhir wajib diisi').refine(
(value) => {
const masaPajak = formatDate(value, 'YYYY-MM');
const masaSekarang = formatDate(new Date(), 'YYYY-MM');
return masaPajak >= masaSekarang;
},
{ message: 'Masa Pajak Akhir tidak boleh kurang dari Masa Pajak Awal' }
),
masaPajakAkhir: z.string('Masa Pajak Akhir wajib diisi'),
kdObjPjk: z
.object({
label: z.string(),
......@@ -189,6 +185,20 @@ const TahunanA1Schema = z
message: 'Masa Pajak Awal tidak boleh 12 dan tidak boleh lebih dari masa pajak akhir',
path: ['masaPajakAwal'],
}
)
.refine(
(data) => {
const bulan = dayjs(data.masaPajakAwal).get('month') + 1;
const bulanAkhir = dayjs(data.masaPajakAkhir, 'MM').get('month') + 1;
if (bulan === 12 && bulanAkhir < bulan) {
return false;
}
return true;
},
{
message: 'Masa Pajak Akhir tidak boleh kurang dari masa pajak awal',
path: ['masaPajakAkhir'],
}
)
.refine(
(data) => {
......@@ -243,6 +253,11 @@ const TahunanA1Schema = z
}
);
const genderOptions = [
{ value: KELAMIN.LAKI, label: KELAMIN_TEXT[KELAMIN.LAKI] },
{ value: KELAMIN.PEREMPUAN, label: KELAMIN_TEXT[KELAMIN.PEREMPUAN] },
];
export const TahunanA1RekamView = () => {
const { id, type } = useParams<{ id?: string; type?: 'ubah' | 'pengganti' | 'new' }>();
const navigate = useNavigate();
......@@ -377,28 +392,71 @@ export const TahunanA1RekamView = () => {
defaultValues,
});
console.log("🚀 ~ TahunanA1RekamView:", {data:methods.getValues(), error:methods.formState.errors});
console.log('🚀 ~ TahunanA1RekamView:', {
data: methods.getValues(),
error: methods.formState.errors,
});
useEffect(() => {
if ((isEdit || isPengganti) && existingBulanan.data.length !== 0) {
const dataResDetail = existingBulanan.data[0];
const jnsKelamin = dataResDetail.jnsKelamin === 'M' ? '0' : '1';
const statusPtkp = `${dataResDetail.statusPtkp}/${dataResDetail.jmlPtkp}`;
const normalized = {
...dataResDetail,
alamatDipotong: dataResDetail.alamat,
idBupot: dataResDetail.idBupot || '',
noBupot: dataResDetail.noBupot || '',
revNo: dataResDetail.revNo || '',
nitku: dataResDetail.nik,
optJnsKelamin: genderOptions.filter((val) => val.value === jnsKelamin)[0] || {
value: '',
label: '',
},
namaJabatan: dataResDetail.posisiJabatan,
email: dataResDetail.email || '',
fgKaryawanAsing: !!dataResDetail.countryCode,
statusPtkp: ptkpOptions.filter((val) => val.value === statusPtkp)[0] || {
value: '',
label: '',
},
fgKaryawanAsing: dataResDetail.fgKaryawanAsing === 'true',
kodeNegara: kodeNegetaOptions.filter(
(val) => val.value === dataResDetail.countryCode
(val) => val.value === dataResDetail.kdNegara
)[0] || {
value: '',
label: '',
},
passport: dataResDetail.passportNo || '',
metodePemotongan: `${dataResDetail.fgStatusPemotonganPph}`,
kdObjPjk: dataListKOP.filter((val) => val.value === dataResDetail.kodeObjekPajak)[0],
fgFasilitas: fgFasilitasOptions.filter((val) => val.value === dataResDetail.fgFasilitas)[0],
ptkp: ptkpOptions.filter(
(val) => val.value === `${dataResDetail.statusPtkp}/${dataResDetail.jmlPtkp}`
)[0],
noDokLainnya: dataResDetail.noDokFasilitas,
// Financial Details (Rincian)
rincian1: `${dataResDetail.gajiPensiun || 0}`,
rincian2: `${dataResDetail.tunjanganPPh || 0}`,
rincian3: `${dataResDetail.tunjanganLainnyaLembur || 0}`,
rincian4: `${dataResDetail.honorarium || 0}`,
rincian5: `${dataResDetail.premiAsuransi || 0}`,
rincian6: `${dataResDetail.natura || 0}`,
rincian7: `${dataResDetail.tantiemBonus || 0}`,
rincian8: `${dataResDetail.totalPenghasilanBruto || 0}`,
rincian9: `${dataResDetail.biayaJabatan || 0}`,
rincian10: `${dataResDetail.iuranPensiun || 0}`,
rincian11: `${dataResDetail.zakat || 0}`,
rincian12: `${dataResDetail.totalPengurang || 0}`,
rincian13: `${dataResDetail.totalPenghasilanNeto || 0}`,
rincian14: `${dataResDetail.totalPenghasilanNetoDariBupotSebelumnya || 0}`,
rincian15: `${dataResDetail.totalPenghasilanNetoPph21 || 0}`,
rincian16: `${dataResDetail.nominalPtkp || 0}`,
rincian17: `${dataResDetail.pkpSetahunDisetahunkan || 0}`,
rincian18: `${dataResDetail.pph21SetahunDisetahunkan || 0}`,
rincian19: `${dataResDetail.pph21Terutang || 0}`,
rincian20: `${dataResDetail.pph21DariBupotSebelumnya || 0}`,
rincian21: `${dataResDetail.pph21DapatDikreditkan || 0}`,
rincian22: `${dataResDetail.pph21WithheldDtp || 0}`,
rincian23: `${dataResDetail.pph21KurangLebihBayar || 0}`,
idTku: MockNitku.filter((val) => val.value === dataResDetail.idTku)[0],
};
......@@ -418,7 +476,8 @@ export const TahunanA1RekamView = () => {
const transformedData = {
...data,
id: isEdit || isPengganti ? data.id : undefined,
countryCode: data.kodeNegara?.value || null,
fgTransaction: isPengganti ? 'EDIT' : 'NEW',
kdNegara: data.kodeNegara?.value || null,
statusPtkp: data.statusPtkp?.value || '',
kdObjPjk: data.kdObjPjk.value,
kdJnsPjk: 'PPh Pasal 21', // Sesuaikan dengan konstanta Anda
......@@ -429,7 +488,7 @@ export const TahunanA1RekamView = () => {
isPengganti,
};
console.log("🚀 ~ handleDraft ~ transformedData:", transformedData);
console.log('🚀 ~ handleDraft ~ transformedData:', transformedData);
const response = await saveTahunanA1(transformedData);
......
......@@ -6,7 +6,12 @@ import {
import type { AxiosError } from 'axios';
import dayjs from 'dayjs';
import { endpoints, fetcherCetakPDF } from 'src/lib/axios-ctas-box';
import { BUPOT_REFERENSI_TEXT, FG_FASILITAS_PPH_21_TEXT, KODE_OBJEK_PAJAK_TEXT } from './constant';
import {
BUPOT_REFERENSI_TEXT,
FG_FASILITAS_PPH_21_TEXT,
KODE_OBJEK_PAJAK_TEXT,
SETAHUN_TEXT,
} from './constant';
import queryKey from './constant/queryKey';
interface ApiCetakResponse {
......@@ -80,8 +85,8 @@ export interface transFromPramsCetakBupotFinalTidakFinalProps {
}
export interface transFromCetakBupotTahuananA1 {
qrcode: string;
mixcode: string;
qrcode: 'URL';
mixcode: 'KODE';
noBupot: string;
status: string;
fgSatuPemberiKerja: string;
......@@ -226,6 +231,60 @@ const transformParamsFinalTidakFinal = ({
mixcode: 'mixcode',
});
const transformParamsTahunanA1 = (params: any): transFromCetakBupotTahuananA1 => ({
qrcode: 'URL',
mixcode: 'KODE',
noBupot: params.noBupot,
tahunPajak: params.tahunPajak,
masaPajakAkhir: params.masaPajakAkhir,
masaPajakAwal: params.masaPajakAwal,
pasalPPh: params.pasalPPh,
status: 'Proforma',
npwpDipotong: params.npwp,
namaDipotong: params.nama,
fgKaryawanAsing: params.fgKaryawanAsing,
passport: params.passport,
posisiJabatan: params.posisiJabatan,
gajiPensiun: `${params.gajiPensiun}`,
tunjanganPPh: `${params.tunjanganPPh}`,
tunjanganLainnyaLembur: `${params.tunjanganLainnyaLembur}`,
honorarium: `${params.honorarium}`,
premiAsuransi: `${params.premiAsuransi}`,
natura: `${params.natura}`,
tantiemBonus: `${params.tantiemBonus}`,
totalPenghasilanBruto: `${params.totalPenghasilanBruto}`,
biayaJabatan: `${params.biayaJabatan}`,
iuranPensiun: `${params.iuranPensiun || 0}`,
zakat: `${params.zakat || 0}`,
totalPengurang: `${params.totalPengurang}`,
totalPenghasilanNeto: `${params.totalPenghasilanNeto}`,
totalPenghasilanNetoDariBupotSebelumnya: `${params.totalPenghasilanNetoDariBupotSebelumnya}`,
totalPenghasilanNetoPph21: `${params.totalPenghasilanNetoPph21}`,
pkpSetahunDisetahunkan: `${params.pkpSetahunDisetahunkan}`,
pph21SetahunDisetahunkan: `${params.pph21SetahunDisetahunkan}`,
pph21Terutang: `${params.pph21Terutang}`,
pph21DariBupotSebelumnya: `${params.pph21DariBupotSebelumnya}`,
pph21DapatDikreditkan: `${params.pph21DapatDikreditkan}`,
pph21WithheldDtp: `${params.pph21WithheldDtp}`,
pph21KurangLebihBayar: `${params.pph21KurangLebihBayar}`,
fgSatuPemberiKerja: params.fgSatuPemberiKerja,
alamat: params.alamat,
jnsKelamin: params.jnsKelamin === 'M' ? 'Laki - Laki' : 'Perempuan',
statusPtkp: params.statusPtkp,
jmlPtkp: params.jmlPtkp,
kodeObjekPajak: params.kodeObjekPajak,
fgFasilitas: FG_FASILITAS_PPH_21_TEXT[params.fgFasilitas],
nitkuPemotong: params.idTku,
namaPemotong: params.namaPemotong,
tglPemotongan: dayjs(params.tglPemotongan).format('DD/MM/YYYY'),
namaPenandatangan: params.namaPenandatangan,
fgStatusPemotonganPph: SETAHUN_TEXT[params.fgStatusPemotonganPph],
kdNegara: params.kdNegara || '-',
nominalPtkp: `${params.nominalPtkp || 0}`,
npwpPemotong: params.npwpPemotong || '-',
pasalPph: 'PPh Pasal 21',
});
const { bulanan, finalTidakFinal, tahunanA1 } = endpoints.cetak;
const cetakBulanan = async (
......@@ -269,7 +328,7 @@ const cetakTahunanA1 = async (params: any): Promise<ApiCetakResponse> => {
tahunanA1,
{
method: 'POST',
data: params,
data: transformParamsTahunanA1(params),
},
]);
......
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