Skip to main content
BlogERP

Memodelkan Kasbon: Kas Bon Karyawan di Sebuah ERP

Bagaimana saya membangun alur kasbon dari ujung ke ujung di ERP carwash nyata — pengajuan, pencairan dari sesi kas, pelacakan piutang, dan potong gaji otomatis.

Matthews Wong

Software Engineer

Juli 20269 menit baca
Memodelkan Kasbon: Kas Bon Karyawan di Sebuah ERP

Foto oleh JasonParis via Openverse (CC BY 2.0)

Ringkasan Utama

Kasbon adalah pinjaman tunai karyawan khas Indonesia: perusahaan memberi uang sekarang dan menariknya kembali dari gaji berikutnya. Di ERP ia butuh empat tahap terkait — pengajuan dan persetujuan, pencairan dari sesi kas yang terbuka, saldo piutang berjalan, dan potong gaji otomatis yang dibatasi hukum. ERP jadi hampir tak pernah menyediakannya, jadi saya membangunnya sendiri.

Setiap UKM Indonesia yang pernah saya tangani berjalan dengan kasbon. Seorang tukang cuci butuh Rp300.000 sebelum gajian untuk keadaan darurat keluarga, supervisor shift menyetujuinya, uang langsung keluar dari laci kasir, dan diam-diam terpotong dari gaji bulan depan. Tanpa bunga, tanpa kontrak pinjaman formal — hanya kepercayaan dan catatan di buku. Ketika saya membangun ERP JID Carwash, pertanyaan pertama pemiliknya bukan soal laporan atau inventaris. Melainkan: bisakah sistem menangani kasbon.

Pertanyaan itu lebih sulit dari kelihatannya. Kasbon menyentuh penanganan kas, data karyawan, piutang akuntansi, dan penggajian sekaligus, dan harus tetap benar bahkan saat shift ditutup di tengah transaksi atau ponsel kehilangan sinyal di lapangan. Berikut cara saya memodelkan semuanya di NestJS, Prisma, dan PostgreSQL — beserta invarian yang menjaganya agar tidak menguapkan uang.

Mengapa ERP jadi tidak punya kasbon

Kasbon adalah piutang dari karyawan, dilunasi dengan memotong upah — akuntan menyebutnya uang muka karyawan atau piutang lain-lain, dan ia berada di sisi aset lancar neraca. ERP global memodelkan pinjaman karyawan sebagai jadwal pinjaman penuh dengan bunga dan amortisasi. Itu bentuk yang salah. Kasbon tidak berbunga, penyetujunya informal, dan pelunasannya sekadar dikurangkan dari penggajian berikutnya. Memaksakannya ke modul pinjaman berarti jadwal bunga-nol palsu dan mesin gaji yang tidak tahu cara menetralkan uang muka terhadap gaji bersih.

Model data: kasbon sebagai entitas kelas satu

Saya memberi kasbon modul Prisma-nya sendiri alih-alih menempelkannya ke penggajian. Record intinya menyimpan siapa yang mengajukan, jumlah dalam rupiah bilangan bulat, statusnya, dan — yang krusial — cakupan cabang, karena setiap kueri daftar dan detail di basis kode ini menyaring berdasarkan branchId. Cakupan cabang itu bukan hiasan: itu perbaikan untuk bug IDOR yang saya temui di awal, di mana supervisor satu cabang bisa membaca record kasbon cabang lain hanya dengan mengubah id di URL.

model Kasbon {
  id          String        @id @default(cuid())
  branchId    String        // every query scopes by this (IDOR fix)
  employeeId  String
  amount      Int           // integer IDR — never a float
  reason      String
  status      KasbonStatus  @default(PENDING)
  approvedBy  String?
  disbursedAt DateTime?
  // outstanding piutang is DERIVED, not stored here
  deductions  KasbonDeduction[]
  createdAt   DateTime      @default(now())

  @@index([branchId, status])
}

enum KasbonStatus {
  PENDING
  APPROVED
  DISBURSED
  SETTLING   // partially deducted across payroll runs
  SETTLED
  REJECTED
}

Uang adalah bilangan bulat rupiah biasa, tidak pernah nilai floating-point. Uang muka Rp300.000 disimpan sebagai 300000. Float diam-diam kehilangan rupiah terakhir saat pembagian — dan potong gaji terus-menerus membagi. Satu kolom float saja cukup untuk membuat saldo tak pernah mencapai nol persis.

Tahap satu: pengajuan dan persetujuan

Sebuah kasbon lahir sebagai PENDING. Karyawan mengajukannya dari aplikasi lapangan Flutter atau supervisor memasukkannya di POS. Persetujuan adalah transisi status, bukan alur tanda tangan — UKM Indonesia tidak mau rantai lima langkah untuk uang muka Rp200.000. Namun saya tetap menegakkan beberapa aturan, dan setiap transisi ditulis ke AuditLog yang hanya-tambah dengan PII disunting, sehingga pemilik selalu bisa menjawab siapa menyetujui apa.

  • Pengajuan tidak boleh melebihi plafon yang dapat dikonfigurasi — biasanya setengah gaji bulanan, sesuai praktik umum UKM.
  • Karyawan tidak boleh memegang dua kasbon yang belum lunas di cabang yang sama kecuali pemilik menimpanya.
  • Hanya kasbon APPROVED yang bisa dicairkan; hanya kasbon DISBURSED yang masuk potong gaji.

Tahap dua: pencairan dari sesi kas

Di sinilah kasbon berhenti menjadi konsep SDM dan menjadi uang tunai nyata yang keluar dari laci. Pencairan hanya diizinkan terhadap sesi kas yang terbuka — record shift yang melacak uang fisik di laci. Membayar kasbon menulis pergerakan kas-keluar yang terikat ke sesi itu, sehingga saat kasir menghitung laci di penutupan, kasbon sudah terhitung dan hitungan tetap seimbang. Jika tidak ada sesi terbuka, pencairan ditolak; Anda tidak bisa mengeluarkan uang yang tidak menjadi tanggung jawab shift mana pun.

Buku kas (CashLedgerEntry) di sistem ini adalah tampilan gabungan turunan, bukan tabel yang ditulis. Pencairan kasbon, penjualan, dan pengeluaran masing-masing disimpan di tabelnya sendiri; buku kas sekadar menggabungkannya saat dibaca. Artinya pencairan kasbon muncul di buku kas otomatis begitu dicairkan — tidak ada penulisan kedua untuk disinkronkan dan tidak ada yang bisa menyimpang.

Tahap tiga: saldo piutang diturunkan, bukan disimpan

Kesalahan paling menggoda adalah menyimpan kolom outstandingAmount di kasbon dan menguranginya setiap potongan. Saya menolaknya. Piutang terutang selalu dihitung sebagai jumlah yang dicairkan dikurangi total semua potongan tercatat. Saldo tersimpan adalah cache yang bisa berbohong begitu dua penggajian berlomba atau sebuah potongan dibalik. Saldo turunan tidak bisa — ini pola yang sama yang saya pakai untuk stok, di mana Product.stockQty hanya cache dan SUM pergerakan stok adalah kebenarannya.

// outstanding piutang = disbursed - sum(deductions)
async outstandingFor(kasbonId: string, branchId: string) {
  const k = await this.prisma.kasbon.findFirstOrThrow({
    where: { id: kasbonId, branchId }, // always branch-scoped
  });
  const agg = await this.prisma.kasbonDeduction.aggregate({
    where: { kasbonId },
    _sum: { amount: true },
  });
  const deducted = agg._sum.amount ?? 0;
  return k.amount - deducted; // integer IDR, always exact
}

Tahap empat: potong gaji otomatis

Ketika penggajian dibuat, mesin menarik setiap kasbon DISBURSED atau SETTLING untuk tiap karyawan di cabang itu dan menetralkan saldo terutang terhadap gaji bersih. Dua aturan penting. Pertama, hukum Indonesia (Peraturan Pemerintah 36/2021) membatasi total potongan upah maksimal 50 persen dari satu periode gaji dan mensyaratkan persetujuan tertulis, jadi mesin tak pernah memotong lebih dari separuh gaji bersih dalam satu penggajian — kasbon besar cukup disebar lintas bulan dan statusnya tetap SETTLING sampai mencapai nol. Kedua, tiap potongan membawa kunci idempotensi yang diturunkan dari kasbon dan periode gaji, pola settlement yang sama yang saya pakai untuk pembayaran, sehingga menjalankan ulang atau mencoba lagi penggajian tak pernah memotong ganda.

AspekModul pinjaman (ERP jadi)Modul kasbon (yang saya bangun)
BungaWajib, dipalsukan nolTidak ada — bukan kolom
PelunasanJadwal amortisasi tetapNetto ke gaji berikutnya, dibatasi 50 persen
PencairanEntri transfer bankKas keluar dari sesi laci terbuka
SaldoDisimpan, dikurangiDiturunkan dari potongan

Hasilnya adalah kasbon yang berperilaku seperti yang sudah dipikirkan pemilik carwash Indonesia — cepat diberikan, terlihat di laci kas, dan hilang dari gaji tanpa siapa pun menyentuh spreadsheet. Di baliknya ia tetap jujur karena uangnya rupiah bilangan bulat, piutangnya selalu dihitung ulang, buku kas menurunkan dirinya sendiri, dan potongan bersifat idempoten serta dibatasi hukum. Tidak ada yang eksotis di situ. Itu sekadar domain yang dimodelkan sebagaimana ia benar-benar bekerja, bukan dibengkokkan agar muat ke modul pinjaman yang dirancang untuk negara lain.

FAQ

Pertanyaan Umum

Jawaban singkat untuk pertanyaan yang paling sering diajukan pembaca tentang topik ini.

Apa itu kasbon dalam bisnis Indonesia?

Kasbon adalah uang muka tunai karyawan — perusahaan memberi karyawan uang sebelum gajian dan menariknya kembali dari gaji berikutnya. Ia tanpa bunga dan tanpa kontrak pinjaman formal, berbeda dari pinjaman bank. Secara akuntansi ia adalah piutang dari karyawan (piutang karyawan), dicatat sebagai aset lancar sampai lunas.

Bagaimana kasbon dilunasi?

Kasbon dilunasi dengan memotong jumlahnya dari pembayaran gaji karyawan berikutnya, bukan lewat cicilan terpisah. Jika uang muka besar, potongan disebar ke beberapa periode gaji. Di ERP saya mesin penggajian menetralkan saldo terutang terhadap gaji bersih secara otomatis setiap penggajian.

Mengapa tidak memakai saja modul pinjaman ERP untuk kasbon?

Modul pinjaman mengasumsikan bunga, jadwal amortisasi tetap, dan pencairan bank — semuanya tidak cocok untuk kasbon. Kasbon tanpa bunga, penyetujunya informal, uang dibayar dari laci kasir, dan pelunasannya sekadar dikurangkan dari gaji. Memaksakannya ke modul pinjaman menghasilkan jadwal bunga-nol palsu dan mesin gaji yang tak bisa menetralkan uang muka dengan benar.

Berapa banyak gaji yang boleh dipotong untuk kasbon di Indonesia?

Peraturan Pemerintah 36/2021 membatasi total potongan upah, termasuk pelunasan utang karyawan, maksimal 50 persen dari satu kali pembayaran upah, dan mensyaratkan persetujuan tertulis dari karyawan. ERP saya menegakkannya dengan tak pernah memotong lebih dari separuh gaji bersih dalam satu penggajian, menyebar kasbon besar lintas bulan sampai lunas sepenuhnya.

Mengapa menyimpan saldo kasbon sebagai turunan alih-alih kolom tersimpan?

Saldo tersimpan adalah cache yang bisa menyimpang jika dua penggajian berlomba atau sebuah potongan dibalik. Menghitung piutang terutang sebagai jumlah dicairkan dikurangi total semua potongan selalu tepat. Ini pola sumber-kebenaran yang sama yang dipakai untuk stok, di mana total pergerakan bersifat otoritatif dan kolom kuantitas hanya cache.