Skip to main content

Ekstrak Data Pajak dari PDF ke Excel Otomatis

Ekstrak Data Pajak dari PDF ke Excel Secara Otomatis dengan Python

Solusi otomatisasi untuk administrasi keuangan sekolah atau instansi

Bagi bendahara sekolah atau staf administrasi, mengolah data pajak dari file PDF BKU ke format Excel bisa sangat memakan waktu jika dilakukan manual. Solusinya, kita bisa otomatisasi proses ini menggunakan Python.

Tool ini akan membaca file PDF BKU, mengekstrak data pajak (PPh 21, PPh 23, PPN, dll), dan menyimpan ke Excel lengkap dengan filter, dropdown, conditional formatting, dan summary otomatis.

1. Persiapan & Instalasi Python

Pastikan Python sudah terpasang di komputer Anda. Cek di terminal/cmd:

python --version

Jika belum ada, unduh dari python.org.

Install Library Tambahan
pip install pdfplumber pandas openpyxl tqdm
  • pdfplumber → membaca tabel dari PDF
  • pandas → memproses data
  • openpyxl → membuat & memformat Excel
  • tqdm → progress bar saat proses

2. Cara Kerja Tool

  • Mencari semua file PDF di folder
  • Membaca data pajak dari setiap PDF
  • Menyimpan hasil ke file Excel dengan fitur:
    • Dropdown ✅❌ untuk status pembayaran
    • Tanggal otomatis saat pilih sudah dibayar
    • Warna hijau untuk sudah dibayar, kuning untuk terlambat
    • Summary otomatis: total pajak, sudah/belum dibayar, persentase
    • Filter & sorting bawaan Excel
    • Instruksi penggunaan langsung di sheet

3. Menjalankan Script

  1. Simpan script ekstrak_pajak.py di folder yang sama dengan file PDF.
  2. 
    import pdfplumber
    import pandas as pd
    import os
    import glob
    import openpyxl
    from openpyxl.worksheet.datavalidation import DataValidation
    from openpyxl.styles import PatternFill, Font, Alignment, Border, Side
    from openpyxl.formatting.rule import FormulaRule
    from datetime import datetime
    import subprocess
    
    # === 1. SMART PDF FILE SELECTION ===
    def select_pdf_file():
        """Fungsi untuk memilih file PDF secara smart"""
        
        # Cari semua file PDF di direktori current
        pdf_files = glob.glob("*.pdf")
        
        if not pdf_files:
            print("❌ Tidak ada file PDF di folder ini!")
            print("   Pastikan file PDF berada di folder yang sama dengan script")
            return None
        
        # Filter hanya file BKU (case insensitive)
        bku_files = [f for f in pdf_files if 'bku' in f.lower()]
        
        if bku_files:
            print("📁 File BKU yang ditemukan:")
            for i, file in enumerate(bku_files, 1):
                file_size = os.path.getsize(file) / 1024  # KB
                print(f"   {i}. {file} ({file_size:.1f} KB)")
            
            if len(bku_files) == 1:
                choice = 1
                print(f"   ✅ Otomatis memilih: {bku_files[0]}")
            else:
                try:
                    choice = int(input("   Pilih nomor file (1-{}): ".format(len(bku_files))))
                    if choice < 1 or choice > len(bku_files):
                        raise ValueError
                except (ValueError, IndexError):
                    print("   ⚠️  Pilihan tidak valid, menggunakan file pertama")
                    choice = 1
            
            return bku_files[choice-1]
        
        else:
            print("📁 File PDF yang ditemukan:")
            for i, file in enumerate(pdf_files, 1):
                file_size = os.path.getsize(file) / 1024
                print(f"   {i}. {file} ({file_size:.1f} KB)")
            
            try:
                choice = int(input("   Pilih nomor file (1-{}): ".format(len(pdf_files))))
                if choice < 1 or choice > len(pdf_files):
                    raise ValueError
                return pdf_files[choice-1]
            except (ValueError, IndexError):
                print("   ⚠️  Pilihan tidak valid, menggunakan file pertama")
                return pdf_files[0]
    
    # === 2. VALIDATE PDF FILE ===
    def validate_pdf(file_path):
        """Validasi bahwa file PDF bisa dibaca"""
        try:
            with pdfplumber.open(file_path) as pdf:
                if len(pdf.pages) == 0:
                    return False, "File PDF kosong"
                return True, "OK"
        except Exception as e:
            return False, f"Error membaca PDF: {str(e)}"
    
    # === MAIN EXECUTION ===
    print("=" * 50)
    print("📋 BKU Pajak Extractor")
    print("=" * 50)
    
    # Pilih file PDF
    pdf_file = select_pdf_file()
    if not pdf_file:
        exit()
    
    # Validasi file
    is_valid, message = validate_pdf(pdf_file)
    if not is_valid:
        print(f"❌ {message}")
        exit()
    
    print(f"✅ File dipilih: {pdf_file}")
    print(f"   Size: {os.path.getsize(pdf_file) / 1024:.1f} KB")
    print(f"   Dibaca: {datetime.now().strftime('%d/%m/%Y %H:%M')}")
    print("-" * 50)
    
    # === 3. EKSTRAK DATA DARI PDF ===
    data = []
    last_bukti = None
    total_pages = 0
    extracted_count = 0
    
    try:
        with pdfplumber.open(pdf_file) as pdf:
            total_pages = len(pdf.pages)
            print(f"📄 Membaca {total_pages} halaman...")
            
            for page_num, page in enumerate(pdf.pages, 1):
                print(f"   Processing page {page_num}/{total_pages}", end="\r")
                
                table = page.extract_table()
                if table:
                    for row in table[1:]:  # Skip header
                        if len(row) >= 8:  # Pastikan ada 8 kolom
                            tanggal, kode_keg, kode_rek, no_bukti, uraian, penerimaan, pengeluaran, saldo = row[:8]
                            
                            if no_bukti and no_bukti.strip():
                                last_bukti = no_bukti.strip()
                            
                            if (uraian and "Terima" in uraian and 
                                any(pajak in uraian for pajak in ["PPh 21", "PPh 23", "PPN", "PPh 4(2)", "PPh Final"])):
                                
                                # Deteksi jenis pajak
                                if "PPh 21" in uraian:
                                    jenis_pajak = "PPh 21"
                                elif "PPh 23" in uraian:
                                    jenis_pajak = "PPh 23"
                                elif "PPN" in uraian:
                                    jenis_pajak = "PPN"
                                elif "PPh 4(2)" in uraian or "PPh Final" in uraian:
                                    jenis_pajak = "PPh Final"
                                else:
                                    jenis_pajak = "Pajak Lainnya"
                                
                                data.append({
                                    "NO. BUKTI": last_bukti,
                                    "URAIAN": uraian.strip(),
                                    "JENIS PAJAK": jenis_pajak,
                                    "PENERIMAAN": penerimaan.strip() if penerimaan else "0",
                                    "SUDAH DIBAYAR": "❌",
                                    "TANGGAL BAYAR": ""
                                })
                                extracted_count += 1
    
        print(f"\n✅ Berhasil ekstrak {extracted_count} data pajak")
    
    except Exception as e:
        print(f"\n❌ Error saat membaca PDF: {e}")
        exit()
    
    # === 4. SIMPAN KE EXCEL ===
    if not data:
        print("❌ Tidak ada data pajak yang ditemukan dalam file PDF!")
        print("   Pastikan format BKU sesuai dan mengandung kata 'Terima' dengan jenis pajak")
        exit()
    
    nama_file = os.path.splitext(pdf_file)[0]
    excel_file = f"{nama_file}_pajak.xlsx"
    
    df = pd.DataFrame(data)
    df["PENERIMAAN"] = df["PENERIMAAN"].str.replace(".", "", regex=False).astype(float)
    df.to_excel(excel_file, index=False)
    
    # === 5. ENHANCED EXCEL FORMATTING ===
    wb = openpyxl.load_workbook(excel_file)
    ws = wb.active
    
    # Header untuk kolom baru
    ws['E1'] = "SUDAH DIBAYAR"
    ws['F1'] = "TANGGAL BAYAR"
    
    # Dropdown validation
    dv = DataValidation(type="list", formula1='"❌,✅"', allow_blank=True)
    dv.add(f"E2:E{len(data)+1}")
    ws.add_data_validation(dv)
    
    # Data validation untuk tanggal
    dv_date = DataValidation(type="date", operator="between", 
                             formula1="DATE(2020,1,1)", 
                             formula2=f"DATE({datetime.now().year+1},12,31)")
    dv_date.add(f"F2:F{len(data)+1}")
    ws.add_data_validation(dv_date)
    
    # Set default values dan formula untuk tanggal otomatis
    for row in range(2, len(data) + 2):
        ws[f'E{row}'] = "❌"
        ws[f'F{row}'].number_format = 'DD/MM/YYYY'
        # Tambahkan formula: jika status ✅, isi tanggal hari ini
        ws[f'F{row}'] = f'=IF(E{row}="✅", TODAY(), "")'
    
    # Conditional formatting
    green_fill = PatternFill(start_color="C6EFCE", end_color="C6EFCE", fill_type="solid")
    yellow_fill = PatternFill(start_color="FFEB9C", end_color="FFEB9C", fill_type="solid")
    
    for row in range(2, len(data) + 2):
        # Hijau untuk sudah dibayar
        rule_green = FormulaRule(formula=[f'$E{row}="✅"'], fill=green_fill)
        ws.conditional_formatting.add(f'A{row}:F{row}', rule_green)
        
        # Kuning untuk belum dibayar tapi sudah lewat 30 hari dari sekarang
        rule_yellow = FormulaRule(formula=[f'AND($E{row}="❌", F{row}<>"", TODAY()-F{row}>30)'], fill=yellow_fill)
        ws.conditional_formatting.add(f'A{row}:F{row}', rule_yellow)
    
    # Style header
    header_fill = PatternFill(start_color="366092", end_color="366092", fill_type="solid")
    header_font = Font(color="FFFFFF", bold=True)
    thin_border = Border(left=Side(style='thin'), 
                         right=Side(style='thin'), 
                         top=Side(style='thin'), 
                         bottom=Side(style='thin'))
    
    for col in range(1, 7):  # A sampai F
        cell = ws.cell(row=1, column=col)
        cell.fill = header_fill
        cell.font = header_font
        cell.border = thin_border
        cell.alignment = Alignment(horizontal="center", vertical="center")
    
    # Auto filter
    ws.auto_filter.ref = ws.dimensions
    
    # Column widths
    widths = [15, 50, 15, 15, 15, 15]
    for col, width in enumerate(widths, 1):
        ws.column_dimensions[openpyxl.utils.get_column_letter(col)].width = width
    
    # Freeze panes
    ws.freeze_panes = "A2"
    
    # Add summary table
    ws['H1'] = "SUMMARY STATUS"
    ws['H2'] = "Total Pajak"
    ws['H3'] = "Sudah Dibayar"
    ws['H4'] = "Belum Dibayar"
    ws['H5'] = "Persentase"
    ws['H6'] = "Terakhir Update"
    
    ws['I1'] = "NILAI"
    ws['I2'] = f"=SUM(D2:D{len(data)+1})"
    ws['I3'] = f"=SUMIF(E2:E{len(data)+1},\"✅\",D2:D{len(data)+1})"
    ws['I4'] = f"=SUMIF(E2:E{len(data)+1},\"❌\",D2:D{len(data)+1})"
    ws['I5'] = f"=I3/I2"
    ws['I6'] = f'=NOW()'
    
    # Format summary
    for row in range(2, 6):
        ws[f'I{row}'].number_format = '#,##0'
    ws['I5'].number_format = '0.00%'
    ws['I6'].number_format = 'DD/MM/YYYY HH:MM'
    
    # Format currency untuk PENERIMAAN
    for row in range(2, len(data) + 2):
        ws[f'D{row}'].number_format = '#,##0'
    
    # Tambahkan instruksi penggunaan
    ws['K1'] = "📋 INSTRUKSI PENGGUNAAN"
    ws['K2'] = "1. Pilih '✅' di kolom E untuk menandai sudah dibayar"
    ws['K3'] = "2. Tanggal akan terisi otomatis dengan hari ini"
    ws['K4'] = "3. Untuk ubah tanggal, edit manual di kolom F"
    ws['K5'] = "4. Filter dengan dropdown di header kolom"
    ws['K6'] = "5. Lihat summary otomatis di kolom H-I"
    
    # Auto width untuk kolom instruksi
    ws.column_dimensions['K'].width = 35
    
    wb.save(excel_file)
    
    # === 6. AUTO OPEN FILE ===
    excel_path = os.path.abspath(excel_file)
    if os.path.exists(excel_path):
        print(f"🔗 Membuka file Excel...")
        try:
            os.startfile(excel_path)
            print(f"✅ File Excel berhasil dibuka!")
        except:
            print(f"❌ Gagal membuka file secara otomatis")
            print(f"   Buka manual: {excel_path}")
    else:
        print(f"❌ File tidak ditemukan: {excel_path}")
    
    print(f"""\n🎉 SUKSES! File '{excel_file}' telah dibuat dengan:
    
    📊 Statistik:
    • File sumber: {pdf_file}
    • Halaman diproses: {total_pages}
    • Data pajak ditemukan: {extracted_count}
    • Jenis pajak: {', '.join(df['JENIS PAJAK'].unique())}
    
    💡 Fitur:
    • Tanggal otomatis ketika pilih ✅
    • Summary lengkap dengan total: Rp {df['PENERIMAAN'].sum():,.0f}
    • Filter, sorting, dan conditional formatting
    • Format professional siap pakai
    
    📍 File disimpan di: {excel_path}
    """)
    
  3. Buka terminal di folder tersebut.
  4. Jalankan:
    python ekstrak_pajak.py
  5. Semua PDF diproses otomatis → Excel baru dibuat dengan nama _pajak.xlsx.

4. Fitur di File Excel

  • Kolom E (SUDAH DIBAYAR): Pilih ✅ → baris hijau otomatis.
  • Kolom F (TANGGAL BAYAR): Tanggal terisi otomatis, bisa ubah manual.
  • Summary di Kolom H-I: Total pajak, sudah/belum dibayar, persentase.
  • Filter di Header: Sortir & filter data dengan mudah.

5. Contoh Output

NO. BUKTIURAIANJENIS PAJAKPENERIMAANSUDAH DIBAYARTANGGAL BAYAR
BPU128Terima PPh 23 …PPh 2340.000
BPU143Terima PPh 21 …PPh 21234.000

6. Penutup

Dengan script ini, pekerjaan mengolah data pajak dari PDF ke Excel jadi jauh lebih cepat, akurat, dan rapi. Tidak perlu copy-paste manual, semua otomatis: dari ekstraksi data sampai summary laporan.

No Comments yet!

Your Email address will not be published.