14 β Desain: Kas, Biaya, dan Pajak¶
Status: DRAFT β belum final, menunggu evaluasi pemilik Dibaca oleh: Database Engineer, Backend Engineer Tujuan: mendokumentasikan keputusan arsitektur untuk modul kas, biaya, dan perpajakan β sebagai perbaikan dari model legacy yang selama 20 tahun menggabungkan fungsi-fungsi ini dalam satu tabel.
1. Temuan dari Legacy¶
1a. Tabel t (transaksi) β field keuangan utama¶
| Field legacy | Kolom baru | Makna |
|---|---|---|
t.nilai |
subtotal |
Sum baris detail saja (sebelum biaya, diskon, pembulatan) |
t.biaya |
(β transaksi_detail BIAYA) | Biaya tambahan β akan pindah ke detail |
t.diskon |
diskon |
Diskon keseluruhan nota |
t.pembulatan |
pembulatan |
Pembulatan |
t.nilaitotal |
total |
Sama persis β sudah termasuk semua komponen |
t.bayar |
dibayar |
Uang muka / DP saat transaksi |
t.rek_kas |
akun_kas_kode |
Akun kas yang menerima pembayaran |
t.rek_kredit |
akun_kredit_kode |
Akun piutang/hutang |
t.kredit |
kredit |
Piutang/hutang awal = total β dibayar |
t.pembayaran |
total_pelunasan |
Total pelunasan yang masuk setelahnya |
t.potongnota |
potongnota |
Kredit dari retur yang dipotong ke nota ini |
t.saldo |
(dihitung) | kredit β total_pelunasan β potongnota |
Rumus total (berlaku di legacy dan skema baru):
total = (subtotal + biaya + pembulatan) β diskon
= (t.nilai + t.biaya + t.pembulatan) β t.diskon
= t.nilaitotal β legacy sudah menghitung ini
Catatan penting untuk ETL (V003): -
totalβt.nilaitotalβ sudah benar di V003 -subtotalβt.nilaiβ salah di V003, saat ini memakait.nilaitotal
1b. Tabel kas legacy β dua peran berbeda dalam satu tabel¶
Query ke database (2026-05-20) menunjukkan bahwa kas.tipe membedakan dua
fungsi yang sangat berbeda:
kas.tipe |
Dipakai kdtrans | Fungsi sebenarnya |
|---|---|---|
KAS |
KK, KM | Buku kas β detail tiap baris pengeluaran/penerimaan |
KREDIT |
PP, PH, OP, OH | Ledger AR/AP β faktur mana yang dilunasi |
kdtrans yang punya kas:
| kdtrans | Jumlah transaksi | Jumlah kas rows | Keterangan |
|---|---|---|---|
| PP | 89.411 | 89.398 | Pelunasan piutang |
| KK | 39.091 | 39.013 | Kas keluar (pengeluaran) |
| PH | 11.156 | 11.154 | Pelunasan hutang |
| KM | 1.538 | 1.533 | Kas masuk (setoran) |
| OP | 246 | 246 | Opening piutang |
| OH | 37 | 37 | Opening hutang |
kdtrans tanpa kas (piutang/hutang disimpan di kolom t.kredit/t.saldo):
PJ, PI, GP, PB, DA, PI, PL, RJ, RB, HU, DA, JU, OS
1c. Pola kas KK/KM (Buku Kas)¶
KK-25851 | dari: 110.11 Kas Pusat | total=45.000
kas row 1: rek=613.00 | uraian="3 Koli Dari Jatayu Sakti"
kas row 2: rek=613.00 | uraian="Ongkos Bongkar"
(field debit/kredit sering NULL β hanya rek + uraian yang terisi)
Jurnal yang dihasilkan:
D 613.00 Biaya Angkut 45.000
K 110.11 Kas Pusat 45.000
1d. Pola kas PP/PH (Ledger AR/AP)¶
PP-65946 | rekkas=110.27 Bank | total=487.600
kas row 1: rek=130.00 | rekkas=110.27 | refidtrans=PJ-200677 | ket="Bayar PJ-200677"
kas row 2: rek=130.00 | rekkas=110.27 | refidtrans=PJ-202025 | ket="Bayar PJ-202025"
(1 PP bisa melunasi banyak faktur PJ sekaligus)
Jurnal yang dihasilkan:
D 110.27 Bank BNI 487.600
K 130.00 Piutang PJ-200677 250.000
K 130.00 Piutang PJ-202025 237.600
2. Masalah di Model Legacy¶
-
Satu tabel dua fungsi β
kasdipakai untuk buku kas sekaligus ledger AR/AP. Query harus selalu filterkas.tipeuntuk tidak salah baca. -
Biaya tidak ada detailnya β
t.biayahanya satu angka di header. Tidak bisa tahu berapa biaya kirim vs biaya packing. -
Uang muka hanya satu akun β
t.bayar+t.rek_kashanya bisa satu rekening. Tidak bisa DP cash + transfer dalam satu nota. -
Saldo disimpan, bukan dihitung β
t.saldobisa tidak sinkron jika ada bug di prosedur update. -
Tidak ada perpajakan β PPN, PPh tidak tercatat sama sekali.
3. Keputusan Arsitektur Baru¶
Prinsip: perbaikan, bukan penggantian β pola yang sudah familiar dipertahankan, kelemahan struktural diperbaiki.
3a. KK/KM β masuk ke transaksi_detail (tipe_baris = BIAYA)¶
Bukti Kas Keluar (BKK) dan Bukti Kas Masuk (BKM) menjadi transaksi biasa.
Detail tiap baris pengeluaran/penerimaan masuk ke transaksi_detail dengan
tipe_baris = 'BIAYA' dan akun_kode diisi (bukan barang_id).
Transaksi KK-001 | jenis_kode=KK | akun_kas_kode=110.11 | total=49.000
transaksi_detail urut=1:
tipe_baris=BIAYA | akun_kode=613.00 | qty=1 | harga=45.000 | ket="Ongkos Bongkar"
transaksi_detail urut=2:
tipe_baris=BIAYA | akun_kode=631.03 | qty=1 | harga=4.000 | ket="Spons Cuci Piring"
Trigger menghasilkan jurnal otomatis:
D 613.00 Biaya Angkut 45.000
D 631.03 Perlengkapan Kantor 4.000
K 110.11 Kas Pusat 49.000
3b. PP/PH β masuk ke transaksi_detail (tipe_baris = PELUNASAN)¶
Transaksi pelunasan piutang/hutang menjadi transaksi biasa. Tiap faktur yang
dilunasi menjadi satu baris transaksi_detail dengan ref_transaksi_id menunjuk
ke transaksi asal (PJ / PI).
Transaksi PP-001 | jenis_kode=PP | akun_kas_kode=110.27 | total=487.600
transaksi_detail urut=1:
tipe_baris=PELUNASAN | akun_kode=130.00 | jumlah=250.000
ref_transaksi_id β PJ-200677.id | ket="Bayar PJ-200677"
transaksi_detail urut=2:
tipe_baris=PELUNASAN | akun_kode=130.00 | jumlah=237.600
ref_transaksi_id β PJ-202025.id | ket="Bayar PJ-202025"
Trigger saat insert PELUNASAN β update total_pelunasan di transaksi asal:
UPDATE transaksi
SET total_pelunasan = total_pelunasan + NEW.jumlah
WHERE id = NEW.ref_transaksi_id;
3c. Saldo AR/AP tetap di header transaksi¶
Kolom kredit dan total_pelunasan di transaksi dipertahankan sebagai
sumber kebenaran yang di-maintain trigger. Saldo bisa langsung di-query tanpa JOIN:
-- Daftar piutang belum lunas (pengganti t.saldo > 0)
SELECT * FROM transaksi
WHERE jenis_kode = 'PJ'
AND kredit > total_pelunasan;
3d. Uang muka β tabel transaksi_bayar β
DIPUTUSKAN¶
Uang muka dicatat di tabel terpisah transaksi_bayar. Mendukung multi-akun
(cash + transfer = beberapa baris untuk satu transaksi).
Transaksi PJ-001 | total=1.000.000 | kredit=500.000
transaksi_bayar:
transaksi_id=PJ-001 | akun_kode=110.01 Kas Kecil | jumlah=300.000 | ket="Tunai"
transaksi_id=PJ-001 | akun_kode=120.07 Bank BNI | jumlah=200.000 | ket="Transfer"
Trigger: transaksi.dibayar = SUM(transaksi_bayar.jumlah) WHERE transaksi_id = this.id
transaksi.kredit = total β dibayar
3e. Biaya ke pelanggan β transaksi_detail (tipe_baris = BIAYA)¶
3f. Potongnota / Retur β logika dari legacy¶
potongnota di header transaksi dihitung otomatis dari detail retur yang
mengarah ke transaksi ini dan menandai potong_nota = true:
-- Legacy:
new.potongnota = SUM(d.total)
FROM d (transaksi_detail retur)
WHERE d.refidtrans = new.id
AND d.potong_nota = true
-- Saldo akhir:
saldo = kredit - pembayaran - potongnota
Di sistem baru: trigger pada transaksi_detail dengan tipe_baris='BARANG'
dan ref_transaksi_id terisi + kolom is_potong_nota = true β update
transaksi.potongnota di transaksi asal.
Kolom yang perlu ditambah di transaksi_detail:
is_potong_nota BOOLEAN DEFAULT false -- khusus untuk baris retur yang memotong nota
Kolom yang perlu ditambah di transaksi:
potongnota numeric(18,2) DEFAULT 0 -- di-maintain trigger dari RJ/RB
3g. GP β Sistem Poin Per Item β DIKLARIFIKASI¶
GP bukan "Giro Pegawai" dalam konteks ini. Kode ini bertahan dari 20 tahun lalu
dan sekarang dipakai untuk pembayaran poin ke sales/agen di akhir bulan.
Mekanisme:
- Setiap transaksi_detail punya kolom poin β poin yang berhak diterima
sales/agen jika nota tersebut lunas.
- Saat PJ lunas (trigger PP/pelunasan) β poin di detail "terbuka" / eligible.
- Di akhir bulan, GP dibuat untuk mencatat pembayaran poin yang sudah eligible.
- GP tanpa kas rows karena ini bukan AR/AP β langsung ke jurnal.
Query monitoring poin:
-- Poin eligible (nota sudah lunas, belum dibayar via GP)
SELECT td.*, t.nomor_transaksi
FROM transaksi_detail td
JOIN transaksi t ON t.id = td.transaksi_id
WHERE t.jenis_kode = 'PJ'
AND t.kredit <= t.total_pelunasan -- sudah lunas
AND td.poin > 0
AND NOT EXISTS (
SELECT 1 FROM transaksi_detail gp
WHERE gp.ref_transaksi_id = t.id
AND gp.tipe_baris = 'POIN'
);
Di sistem baru: GP tetap sebagai transaksi dengan transaksi_detail
bertipe POIN, masing-masing baris mereferensi ref_transaksi_id ke PJ asal.
Biaya kirim, biaya packing yang ditagih ke pelanggan masuk sebagai baris BIAYA
di transaksi_detail PJ yang sama β bukan di header.
Transaksi PJ-002 | total=1.050.000
transaksi_detail:
tipe_baris=BARANG | barang_id=... | jumlah=1.000.000
tipe_baris=BIAYA | akun_kode=pendapatan_kirim | jumlah=50.000 | ket="Ongkir JNE"
4. Perubahan Schema¶
4a. transaksi_detail β tambah 2 kolom¶
ALTER TABLE transaksi_detail
ADD COLUMN akun_kode TEXT,
ADD COLUMN tipe_baris TEXT,
ADD COLUMN is_potong_nota BOOLEAN DEFAULT false;
-- tipe_baris: BARANG | BIAYA | PELUNASAN | PAJAK | DISKON | POIN
-- akun_kode: diisi untuk baris non-barang
-- barang_id: diisi untuk BARANG, NULL untuk yang lain
-- is_potong_nota: true hanya pada detail RJ/RB yang memotong saldo nota asal
4b. transaksi β kolom yang berubah / ditambah¶
| Kolom lama | Kolom baru | Sumber update | Catatan |
|---|---|---|---|
t.nilai |
subtotal |
input | Sum baris detail saja |
t.nilaitotal |
total |
input | (subtotal+biaya+pembulatan)βdiskon |
t.diskon |
diskon |
input | β |
t.pembulatan |
pembulatan |
input | β |
t.bayar |
dibayar |
trigger dari transaksi_bayar |
Sum uang muka |
t.kredit |
kredit |
trigger | total β dibayar |
t.pembayaran |
total_pelunasan |
trigger dari PP/PH detail | Sum pelunasan masuk |
t.potongnota |
potongnota |
trigger dari RJ/RB detail | Sum retur potong nota |
t.saldo |
(dihitung) | β | kredit β total_pelunasan β potongnota |
4c. transaksi_bayar β tabel baru β
DIPUTUSKAN¶
CREATE TABLE transaksi_bayar (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
transaksi_id BIGINT NOT NULL REFERENCES transaksi(id) ON DELETE CASCADE,
akun_kode TEXT NOT NULL REFERENCES akun(kode),
jumlah numeric(18,2) NOT NULL CHECK (jumlah > 0),
keterangan TEXT,
created_at timestamptz DEFAULT now()
);
-- Trigger: AFTER INSERT/UPDATE/DELETE ON transaksi_bayar
-- β UPDATE transaksi SET dibayar = SUM(jumlah) WHERE id = transaksi_id
4d. transaksi_pajak β tabel baru (β³ DITUNDA)¶
Status: placeholder. Pemilik belum memiliki kebutuhan yang cukup jelas untuk PPN/PPh. Tabel ini disiapkan sebagai fondasi; isi dan trigger-nya ditentukan setelah ada diskusi lebih lanjut tentang alur perpajakan.
-- Placeholder β kolom bisa bertambah setelah spesifikasi pajak jelas
CREATE TABLE transaksi_pajak (
id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
transaksi_id BIGINT NOT NULL REFERENCES transaksi(id),
tipe TEXT, -- PPN_KELUARAN | PPN_MASUKAN | PPH_23 | PPH_4_2 | ...
tarif numeric(5,2),
dpp numeric(18,2), -- dasar pengenaan pajak
jumlah numeric(18,2),
akun_kode TEXT,
created_at timestamptz DEFAULT now()
);
-- Kolom e-Faktur/Coretax (npwp_lawan, nomor_seri_faktur, dll) ditambah nanti
5. Alur Lengkap per Tipe Transaksi¶
| kdtrans | Header | Detail (tipe_baris) | Transaksi Bayar | Trigger efek pada |
|---|---|---|---|---|
| PJ | kredit, dibayar, total | BARANG, BIAYA | β (uang muka) | jurnal, stok, fifo, dibayar |
| PI | kredit, dibayar, total | BARANG, BIAYA | β (uang muka) | jurnal, stok, fifo, dibayar |
| KK | akun_kas_kode, total | BIAYA (akun, jumlah) | β | jurnal |
| KM | akun_kas_kode, total | BIAYA (akun, jumlah) | β | jurnal |
| PP | akun_kas_kode, total | PELUNASAN (ref, jumlah) | β | jurnal + PJ.total_pelunasan |
| PH | akun_kas_kode, total | PELUNASAN (ref, jumlah) | β | jurnal + PI.total_pelunasan |
| RJ | ref_transaksi_id | BARANG negatif + is_potong_nota | β | jurnal, restock, PJ.potongnota |
| RB | ref_transaksi_id | BARANG negatif + is_potong_nota | β | jurnal, restock, PI.potongnota |
| GP | β | POIN (ref ke PJ, jumlah poin) | β | jurnal poin + tandai poin terbayar |
6. Yang Belum Diputuskan¶
| Item | Status | Catatan |
|---|---|---|
transaksi_bayar terpisah |
β DIPUTUSKAN | Tabel terpisah, multi-akun |
potongnota |
β DIKLARIFIKASI | Trigger dari RJ/RB detail is_potong_nota=true |
GP = sistem poin |
β DIKLARIFIKASI | Bukan giro, tipe_baris=POIN, bayar poin akhir bulan |
t.biaya β tipe_baris=BIAYA |
β DIPUTUSKAN | Pakai transaksi_detail |
| Perpajakan (PPN/PPh) | β³ DITUNDA | Pemilik belum cukup familiar β placeholder tabel sudah ada |
Fix subtotal di V003 |
β SELESAI | t.nilai β subtotal; t.nilaitotal β total |
| ETL KK/KM/PP/PH historis | β DIPUTUSKAN | History dan jurnal adalah dua hal berbeda β keduanya wajib di-ETL |
7. Dampak ke ETL (V003)¶
Bug yang perlu diperbaiki di V003:
-- V003 baris 59: subtotal salah, memakai nilaitotal padahal seharusnya nilai
-- Saat ini:
COALESCE(t.nilaitotal, 0), -- subtotal (SALAH β ini total, bukan subtotal)
-- Seharusnya:
COALESCE(t.nilai, 0), -- subtotal = sum baris detail saja
-- V003 baris 63: total sudah BENAR
COALESCE(t.nilaitotal, 0), -- total = (subtotal+biaya+pembulatan)βdiskon β
-- (nilaitotal di legacy sudah = formula lengkap)
Perlu konfirmasi: kolom
t.nilaiada distg_transaksi? Cek sebelum apply fix.
ETL tambahan yang diperlukan (belum dibuat):
Prinsip: History dan jurnal adalah dua hal berbeda. Jurnal (ayat akuntansi) sudah termuat. History (siapa bayar apa, nota mana yang dilunasi) perlu ETL terpisah agar lengkap. Script ETL harus aman dijalankan ratusan kali (idempoten).
- V005 β ETL KK/KM β
transaksi+transaksi_detail(tipe_baris=BIAYA) - Sumber:
stg_kastipe KAS joinstg_transaksikdtrans IN ('KK','KM') -
Setiap
kasrow β 1transaksi_detaildenganakun_kode = kas.rek -
V006 β ETL PP/PH β
transaksi+transaksi_detail(tipe_baris=PELUNASAN) - Sumber:
stg_kastipe KREDIT joinstg_transaksikdtrans IN ('PP','PH') -
Setiap
kasrow β 1transaksi_detaildenganref_transaksi_id= transaksi asal -
V007 β ETL OP/OH β saldo awal piutang/hutang di
kas -
Sumber:
stg_kastipe KREDIT joinstg_transaksikdtrans IN ('OP','OH') -
DDL β tambah kolom
akun_kode+tipe_baris+is_potong_notaketransaksi_detail - DDL β buat tabel
transaksi_bayar
8. Catatan Evaluasi Pemilik¶
(kosongkan bagian ini untuk diisi saat review)
Sudah diputuskan:
- [x] transaksi_bayar β tabel terpisah, multi-akun
- [x] KK/KM β transaksi_detail dengan tipe_baris=BIAYA
- [x] PP/PH β transaksi_detail dengan tipe_baris=PELUNASAN + ref_transaksi_id
- [x] potongnota β trigger dari is_potong_nota=true di detail RJ/RB
- [x] GP β sistem poin per item, dibayar akhir bulan via transaksi GP
Masih perlu keputusan:
- [x] Fix subtotal di V003: t.nilai β subtotal β
- [x] ETL historis KK/KM/PP/PH: history dan jurnal berbeda β keduanya di-ETL β
- [x] Perpajakan: disiapkan sekarang β pusat.pajak + transaksi_pajak sudah dibuat β
- [x] saldo: GENERATED ALWAYS AS (kredit - total_pelunasan - potongnota) STORED β
Dibuat: 2026-05-20 β berdasarkan sesi analisis data legacy u1566482_sparepart
File: docs/14-DESAIN-KAS-BIAYA-PAJAK.md