Lewati ke isi

01 β€” Audit Sistem Lama

Dibaca oleh: semua developer, terutama Database Engineer. Tujuan: paham bagaimana sistem lama benar-benar bekerja β€” dan di mana utang teknisnya β€” sebelum menyentuh apa pun. Jangan migrasi apa yang belum Anda pahami.

Audit ini jujur, bukan menghakimi. Desain ini cerdas untuk zamannya dan menopang bisnis 10 tahun. Tujuan kita: mempertahankan kecerdasannya, membuang kerapuhannya.


1. Gambaran Besar Arsitektur

Sistem ini adalah ERP metadata-driven dengan logika di database. Tiga pilar:

Pilar 1 β€” Satu tabel transaksi untuk semua ("Single Table Inheritance")

Semua jenis transaksi (penjualan, pembelian, retur, kas, jurnal, opname, pindah lokasi, dll.) disimpan di satu tabel header t dan satu tabel detail d. Pembeda jenisnya: kolom t.kdtrans (3 huruf, mis. PJ = penjualan).

t  (header transaksi, Β±60 kolom)  ── kdtrans ──►  datakode  (master jenis transaksi)
β”‚
└── d (detail transaksi, Β±45 kolom)

Pilar 2 β€” datakode adalah "mesin konfigurasi"

Tabel datakode mendefinisikan perilaku setiap jenis transaksi: akun jurnal mana yang didebit/dikredit, apakah memengaruhi stok, apakah hitung HPP, nama tabel master/detail, bahkan potongan SQL yang disimpan sebagai teks (sql_biaya). Mengubah 1 baris di datakode mengubah perilaku akuntansi seluruh jenis transaksi.

Pilar 3 β€” Logika hidup di trigger + prosedur

Saat baris masuk ke t/d, trigger otomatis: membuat nomor transaksi, menghitung total, memutakhirkan stok realtime, menghitung HPP secara FIFO, dan memposting jurnal. Aplikasi (Delphi/Node) "cukup" menulis ke t/d; sisanya database yang kerjakan.

Ini kekuatan sekaligus kelemahan. Kekuatan: konsisten, semua aplikasi dapat perlakuan sama. Kelemahan: logika tak terlihat dari kode aplikasi & tak terdokumentasi. Dokumen 05 membongkar logika ini.


2. Tabel Inti (yang WAJIB dipahami)

Tabel Peran Catatan penting
t Header semua transaksi PK id bigint tanpa AUTO_INCREMENT (dibuat manual via fungsi getidtrans()), ~60 kolom, banyak NULL, charset latin1
d Detail semua transaksi PK id varchar(60) berisi gabungan idtrans.idbarang β€” rapuh (lihat Β§4)
datakode Master/konfigurasi jenis transaksi Inti "mesin"; kode 3 huruf jadi PK
s Stok realtime per (idbarang,idlokasi) Diisi ulang oleh prosedur, bukan sumber kebenaran β€” hasil hitung dari d
fifo Lapisan FIFO masuk (untuk HPP) idmasuk, masuk, keluar, sisa, harga
fifokeluar Konsumsi lapisan FIFO saat barang keluar Menghubungkan keluar ↔ lapisan masuk
j Jurnal akuntansi (buku besar) Double entry: rek,debit,kredit. Tanpa PK, tanpa ID
kas Buku kas / piutang-hutang
brg Master barang PK id tanpa AUTO_INCREMENT
ktk Master kontak (pelanggan/supplier/pegawai) Flag c/s/p = customer/supplier/pegawai
rekening Bagan akun (Chart of Accounts) PK kode bertipe decimal(12,2) β€” nomor akun disimpan sebagai angka desimal
brggolongan Pemetaan golongan barang β†’ akun jurnal rek,rekjual,rekhpp,rekopname,...
lokasi Lokasi/gudang
devisi Divisi/bidang usaha
klien Kode klien (kdpc, 3 huruf) Dipakai membentuk nomor transaksi
operator User aplikasi

Daftar Kode Transaksi (datakode) β€” nyata di sistem

kode nama tipe hitung stok hitung HPP
PB Pembelian TRANSAKSI ya ya
PJ Penjualan TRANSAKSI ya ya
RB Retur Pembelian TRANSAKSI ya β€”
RJ Retur Penjualan TRANSAKSI ya ya
PL Pemindahan Lokasi PINDAH ya β€”
OS Stok Opname / Saldo Persediaan OPNAME ya β€”
BB Penggunaan Persediaan (produksi) PRODUKSI ya ya
PO Order Pembelian TRANSAKSI β€” β€”
SO Order Penjualan TRANSAKSI β€” β€”
KK Kas Keluar KEUANGAN β€” β€”
KM Kas Masuk KEUANGAN β€” β€”
KB Kas & Bank KEUANGAN β€” β€”
PH Pelunasan Hutang KEUANGAN β€” β€”
PP Pelunasan Piutang KEUANGAN β€” β€”
PI Piutang KEUANGAN β€” β€”
HU Hutang KEUANGAN β€” β€”
DA Penyusutan KEUANGAN β€” β€”
GP Bayar Poin / Bonus Sales β€” β€” β€”
JU Jurnal Umum β€” β€” β€”
OH/OHP/OP Saldo Awal / Audit Hutang-Piutang OPNAME β€” β€”

Daftar ini = peta fitur bisnis. Setiap kode harus tetap berfungsi di sistem baru.


3. Aliran Logika Saat Transaksi Dibuat (contoh: Penjualan PJ)

Pahami satu alur ini, Anda paham 80% sistem:

Aplikasi INSERT ke t (kdtrans='PJ')
   └─ trigger t_tr_bi (BEFORE INSERT):
        β€’ id = getidtrans()                 (ID manual = max+1)
        β€’ nobukti = getnobukti('PJ')        (no urut per jenis)
        β€’ notrans = kdpc.PJ.nobukti
        β€’ salin setelan terakhir operator (rekening, lokasi, dll)
        β€’ ambil konfigurasi akun dari datakode

Aplikasi INSERT ke d (per barang)
   └─ trigger d_tr_bi (BEFORE INSERT):
        β€’ id = idtrans.idbarang             (PK string gabungan)
        β€’ urut = max(urut)+1 per transaksi
        β€’ ambil harga jual (jual1..jual5 sesuai jharga)
        β€’ cek stok di tabel s; hitung qty & mutasi (mutasi = Β± qty)
        β€’ HPP via a_getfifo()               (rata-rata tertimbang lapisan FIFO)
   └─ trigger d_tr_ai (AFTER INSERT):
        β€’ calc_stok_by_id_il()              (hitung ulang stok realtime ke s)
        β€’ a_setfifo()                       (catat konsumsi lapisan FIFO)

Saat transaksi diposting:
   └─ a_posttrans() β†’ a_setjurnaltransaksi()
        β€’ DELETE jurnal lama transaksi ini
        β€’ INSERT pasangan debit/kredit ke j sesuai datakode + brggolongan

Catatan kunci: - mutasi = pergerakan stok bertanda (+masuk / βˆ’keluar) = barangdebit Γ— qty. Stok realtime = SUM(mutasi) per barang per lokasi. Tabel s hanyalah cache. - HPP FIFO: a_getfifo membaca lapisan fifo (urut tanggal) dan menghitung biaya rata-rata tertimbang dari lapisan yang terpakai. a_setfifo mencatat pemakaiannya. - Kolom cek di t/d dipakai sebagai sinyal kontrol (nilai 56, 66, 7) untuk memerintahkan trigger melewati/menyesuaikan logika. Pola ini rapuh β€” lihat Β§4.


4. Katalog Utang Teknis (diurut berdasarkan tingkat bahaya)

πŸ”΄ Kritis β€” harus diperbaiki saat migrasi

# Masalah Kenapa berbahaya Solusi di sistem baru
K1 ID dibuat manual max(id)+1 (getidtrans, getnobukti) Dua transaksi bersamaan bisa dapat ID/nobukti sama β†’ data tumpang tindih BIGINT GENERATED ALWAYS AS IDENTITY + nomor dokumen via sequence/tabel counter ber-lock
K2 PK d = string idtrans.idbarang Barang sama tak bisa muncul 2 baris dalam 1 transaksi (beda harga/lokasi); rawan tabrakan PK BIGINT identity; keunikan bisnis via constraint terpisah bila perlu
K3 j (jurnal) tanpa PK / tanpa ID Sulit audit, sulit koreksi 1 baris, mudah duplikat id BIGINT IDENTITY, FK ke transaksi, index
K4 Nomor akun rekening.kode bertipe decimal(12,2) Akun "1.10.01" jadi angka; rawan pembulatan & sulit dibaca kode INT (kode Γ— 100: 110.01 β†’ 11001); hierarki numerik bersih: klas 1–9, subklas 110–800, akun 11001–…
K5 Kolom cek sebagai sinyal kontrol trigger Logika tersembunyi di "angka ajaib" (56/66/7); sangat sulit dibaca tim Ganti dengan kolom status eksplisit + parameter fungsi yang jelas
K6 Charset latin1 Karakter non-latin rusak; tidak siap masa depan PostgreSQL UTF8 (default)

🟠 Tinggi β€” sangat mengganggu pemeliharaan

# Masalah Dampak Solusi
T1 Β±150 prosedur & Β±100 trigger tak terdokumentasi Logika tak terlihat; risiko fitur hilang saat migrasi Inventarisasi & dokumentasikan (mulai Dokumen 05); yang tak terpakai dibuang
T2 Banyak view tampilan menumpuk (lihat Β§5) β€” bukan duplikasi tabel Pembaca baru bisa salah sangka itu tabel kembar; sebagian view mungkin tak terpakai lagi Inventaris view, port yang dipakai ke PostgreSQL, buang yang mati. Tabel dasar TIDAK dikonsolidasi karena memang tidak kembar
T3 Tabel t Β±60 kolom, d Β±45 kolom, mayoritas NULL Sulit dipahami; kolom dipakai beda arti tergantung kdtrans Pisah kolom umum vs spesifik; pertimbangkan tabel turunan/JSONB untuk yang jarang
T4 SQL disimpan sebagai data (datakode.sql_biaya, datakode.detail/master) SQL dinamis = sulit di-review, risiko keamanan, sulit dilacak Pindahkan logika ke fungsi PL/pgSQL bernama
T5 Variabel sesi @var dipakai lintas statement di prosedur Rapuh, sulit ditelusuri, tidak aman di koneksi pool Variabel lokal DECLARE dalam fungsi PL/pgSQL
T6 Nama tabel/kolom 1–3 huruf Hanya pembuat yang paham Penamaan jelas (Dokumen 02 & 03)

🟑 Sedang β€” rapikan sambil jalan

# Masalah Solusi
S1 Penamaan campur (created, dibuat, created_date, updated, aksesakhir) Standarkan created_at / updated_at (Dokumen 07)
S2 Banyak tabel temp_* & *_draft & *lite Pisahkan: tabel kerja sementara β‰  skema inti
S3 63 view, sebagian tumpang tindih Inventaris; pertahankan yang dipakai laporan, buang sisanya
S4 tinyint(1) dipakai sebagai boolean & angka campur Tegaskan tipe boolean di skema baru
S5 Tidak ada kolom tenant; multi-tenant lama dipisah secara fisik Sistem baru memakai schema-per-tenant di database erp, distandarkan lewat template & runner (Dokumen 04)

5. Tabel vs View β€” Klarifikasi Penting (BUKAN duplikasi tabel)

Catatan dari pemilik (penting untuk semua pembaca): nama-nama seperti barang, barang_mini, baranglengkap, baranglite, mobile_barang, v_barang* bukan tabel kembar dan bukan kerja yang berantakan. Itu adalah VIEW β€” "jendela" baca yang sengaja dibuat agar aplikasi mudah menampilkan data lengkap tanpa menulis query gabungan yang rumit berulang kali. Ini justru praktik yang baik. Jangan menyimpulkan ini sebagai utang teknis.

Dari 183 objek di database, sebenarnya:

Jenis objek Jumlah Arti
Tabel dasar (BASE TABLE) 120 Tempat data benar-benar disimpan
β€” di antaranya tabel kerja (temp_*,*_draft,*_temp) 21 Sementara, bukan skema inti
β€” sisanya tabel skema "inti" Β±99 Master & transaksi sesungguhnya
View (VIEW) 63 Bukan tabel β€” hanya cara menampilkan data lebih mudah

Jadi gambaran sebenarnya jauh lebih sehat dari kesan "183 tabel berantakan".

Pemetaan yang benar: 1 tabel sumber + keluarga view di atasnya

Konsep Tabel SUMBER (data nyata) View di atasnya (hanya tampilan, bukan duplikat)
Barang brg (tabel; di-FK oleh d) barang, barang_mini, barang_pg, barang_v2, baranglengkap, baranglite, brg_with_stok, mobile_barang, v_barang, v_barang_json β€” semua VIEW
Kontak ktk (tabel; di-FK oleh t) kontak, kontak_lengkap, kontakalamatlengkap, kontaklite, minikontak β€” semua VIEW
Transaksi t (tabel) transaksi, transaksilite, transaksiliteperiode, transaksi_draft β€” semua VIEW
Detail d (tabel) detail, detail_draft β€” VIEW
Stok s (tabel cache; sumber = d.mutasi) stok, stoklite, stokperiode, rekapstok, cek_stok, v_stok β€” semua VIEW
Harga harga, diskon, diskond (tabel terpisah, fungsi beda) a_harga, a_harga_master, a_harga_rekomendasi β€” VIEW
Tabel kerja transaksi t_draft, t_temp, temp_trans, d_draft, d_retur, temp_d (tabel kerja nyata) β€”

Cara membaca tabel ini: kolom tengah = satu tempat data sebenarnya disimpan (sumber kebenaran). Kolom kanan = view yang membaca dari sumber itu untuk memudahkan tampilan/laporan/aplikasi mobile. Tidak ada konsolidasi data yang perlu dilakukan di sini β€” datanya sudah tunggal.

Implikasi untuk migrasi (yang benar)

  • View tidak menyimpan data, jadi tidak perlu di-ETL. Yang perlu: menulis ulang definisi view yang masih dipakai ke sintaks PostgreSQL (Dokumen 06).
  • Inventaris view: tandai mana yang masih dipakai aplikasi/laporan vs yang sudah mati β†’ yang mati dibuang, yang hidup di-port.
  • Untuk tabel dasar (kolom tengah): buktikan dengan query bahwa memang itu satu-satunya sumber (cek FK & trigger). Berdasarkan analisis, brg/ktk/t/d/s jelas kanonik karena dipakai sebagai target FOREIGN KEY oleh tabel inti lain.

Tindakan: fokus inventaris ke view (mana dipakai/mati), bukan ke "konsolidasi tabel kembar" β€” karena tabelnya memang tidak kembar. Catat hasil di Dokumen 02.


6. Apa yang Sudah BAIK (jangan dirusak saat migrasi)

  • Model metadata-driven datakode sangat fleksibel untuk banyak jenis usaha (dagang/produksi/jasa). Konsep ini dipertahankan, hanya dirapikan.
  • Trigger-first memberi konsistensi: semua aplikasi (Delphi/Flutter) dapat perlakuan akuntansi yang sama. Ini sesuai keinginan pemilik dan tetap dipakai.
  • FIFO HPP berlapis (fifo/fifokeluar) sudah benar secara akuntansi. Logikanya dipindah apa adanya ke PL/pgSQL.
  • Stok: pola cache + jangkar rekonsiliasi (klarifikasi pemilik) β€” ini desain yang benar & disengaja, bukan sekadar cache:
  • s (per idlokasi) & brg.stok (total) = sumber tampilan realtime & cepat yang disukai pengguna; dijaga trigger.
  • Sumber kebenaran = SUM(mutasi) dari detail.
  • Audit cepat: hitung ulang dari mutasi lalu bandingkan dengan s; jika beda β†’ ada transaksi/logika salah, lalu ditelusuri. Di antara ribuan transaksi, inilah cara menemukan 1 kasus yang menyimpang.
  • Dipertahankan, dan dinaikkan statusnya jadi fitur terdokumentasi (fungsi rekonsiliasi resmi) di Dokumen 03 & 05 β€” bukan trik manual.

7. Tindak Lanjut (sebelum desain skema baru difinalkan)

  1. Inventarisasi prosedur & trigger: buat daftar Β±150 prosedur β†’ tandai dipakai / tidak dipakai / duplikat. Tanpa ini, estimasi waktu tak akurat.
  2. Inventaris view (Β§5): tandai view mana yang masih dipakai aplikasi/laporan vs yang sudah mati. View hidup di-port ke PostgreSQL; yang mati dibuang. (Bukan "konsolidasi tabel" β€” tabel dasar tidak kembar.)
  3. Identifikasi semua arti kolom cek (angka 7/56/66/...) β€” ini logika tersembunyi.
  4. Tarik daftar laporan yang dipakai (dari 63 view + prosedur periodic*, omset*, neraca*, rugilaba*) β†’ ini kontrak fungsional yang harus tetap jalan.
  5. Hasil 1–4 dicatat ke Dokumen 02 (Kamus Data) sebagai sumber kebenaran tim.

Perintah bantu (read-only) untuk inventaris ada di Dokumen 06 Β§Lampiran.