Ringkasan Utama
Fitur persetujuan pertama yang dirilis untuk ANCoraPRO menuliskan rantainya langsung di dalam kode service: supervisor, lalu manajer, lalu finance. Setiap aturan baru kemudian menuntut deployment. Memindahkan rantai itu ke matriks yang bisa dikonfigurasi mengubah perubahan alur persetujuan dari pekerjaan engineering menjadi pekerjaan administratif.
Fitur approval pertama yang saya rilis untuk ANCoraPRO, ERP yang saya bangun untuk klien nyata dengan React, NestJS, dan PostgreSQL, rantai approval-nya tertulis langsung di kode service: supervisor, lalu finance manager, lalu direktur. Berjalan sempurna selama tepat empat bulan — sampai klien melakukan restrukturisasi, menambah departemen procurement, dan ingin invoice di atas nominal tertentu butuh dua tanda tangan finance. Setiap perubahan itu berarti deployment kode.
Penulisan ulangnya mengajarkan pelajaran yang menjadi inti artikel ini: logika approval adalah konfigurasi bisnis, bukan kode aplikasi. Deliverable-nya adalah approval matrix — aturan yang disimpan sebagai data, dikunci pada band nominal, jenis dokumen, role, dan departemen — yang bisa diedit admin dari layar. Berikut cara saya mendesainnya sekarang, lengkap dengan skema, plus edge case yang pasti akan menemukan Anda di production, didesain atau tidak.
Kenapa Rantai Hardcoded Selalu Kalah
Hardcode terasa lebih cepat di minggu pertama, dan untuk rantai dua langkah memang benar. Tapi organisasi mengganti approver jauh lebih sering daripada perkiraan developer — promosi, resign, restrukturisasi, temuan audit, kebijakan pengeluaran baru. Bandingkan kedua pendekatan secara jujur dan matrix menang di mana-mana kecuali effort awal:
| Aspek | Rantai hardcoded | Matrix configurable |
|---|---|---|
| Mengganti approver atau threshold | Perubahan kode, review, deployment — berhari-hari, dan selamanya butuh developer. | Admin mengedit baris rule di layar — hitungan menit, finance yang memegang kendali. |
| Restrukturisasi organisasi | Tulis ulang semua branch terdampak; risiko regresi tinggi di alur yang tidak disentuh. | Nonaktifkan rule lama, masukkan rule baru dengan tanggal berlaku; dokumen lama tetap menyimpan historinya. |
| Menjawab audit — siapa boleh approve apa, kapan | Membaca git history dan berdoa tanggal deploy cocok dengan pertanyaannya. | Query baris rule dengan valid_from dan valid_until; jawabannya adalah data. |
| Effort build awal | Rendah — rantai if dan dua cek role. | Lebih tinggi — tabel rule, engine resolusi, UI admin. Balik modal di reorg pertama. |
Empat Dimensi Approval Matrix yang Sesungguhnya
Setiap requirement approval yang pernah saya kumpulkan dari tim finance terurai menjadi empat dimensi. Modelkan keempatnya secara eksplisit dan hampir semua kebijakan muat tanpa perubahan skema:
Jenis dokumen
Purchase order, invoice AP, klaim biaya, pengajuan cuti. Tiap jenis punya rule set sendiri — matrix PO dan matrix expense jarang berbagi threshold.
Band nominal
Rentang half-open dalam IDR: nol sampai 10 juta, 10 sampai 100 juta, 100 juta ke atas. Interval half-open (min inklusif, max eksklusif) mencegah double-match klasik di angka batas.
Departemen atau cost center
Procurement approve dengan cara berbeda dari marketing. Departemen NULL pada rule berarti berlaku se-perusahaan, dan rule departemen spesifik meng-override-nya.
Definisi step approver
Setiap rule yang cocok menghasilkan step berurutan. Step menunjuk ke role, user bernama, atau relasi seperti manager-dari-pemohon, plus quorum untuk approval N-dari-M.
Dokumentasi workflow Dynamics 365 dari Microsoft menggambarkan bentuk persis ini di contoh expense report-nya: laporan 7.000 dolar melewati dua approver, yang 11.000 dolar menambah approver ketiga. Threshold dan partisipannya adalah konfigurasi workflow, bukan logika ter-compile — itu standar yang dipasang sistem besar, dan ERP custom tidak boleh lebih buruk.
Skema yang Tahan Lama: Rule, Step, dan Resolusi
Dua tabel menanggung seluruh desain: approval_rule menyimpan dimensi pencocokan, approval_rule_step menyimpan approver berurutan untuk tiap rule. Resolusi memilih rule aktif paling spesifik — departemen spesifik mengalahkan se-perusahaan, lalu priority eksplisit memutus seri:
-- Rules are data, not code
CREATE TABLE approval_rule (
id BIGSERIAL PRIMARY KEY,
document_type TEXT NOT NULL, -- 'PO' | 'AP_INVOICE' | 'EXPENSE'
department_id BIGINT REFERENCES department(id), -- NULL = any
amount_min NUMERIC(18,0) NOT NULL DEFAULT 0, -- IDR, inclusive
amount_max NUMERIC(18,0), -- NULL = no cap
priority INT NOT NULL DEFAULT 100, -- lower wins on overlap
is_active BOOLEAN NOT NULL DEFAULT true,
valid_from DATE NOT NULL,
valid_until DATE -- NULL = open ended
);
CREATE TABLE approval_rule_step (
id BIGSERIAL PRIMARY KEY,
rule_id BIGINT NOT NULL REFERENCES approval_rule(id),
step_order INT NOT NULL, -- 1, 2, 3...
approver_type TEXT NOT NULL, -- 'ROLE' | 'USER' | 'MANAGER_OF'
approver_ref BIGINT NOT NULL,
quorum INT NOT NULL DEFAULT 1, -- N-of-M approvers
UNIQUE (rule_id, step_order)
);
-- Resolution: most specific active rule wins
SELECT r.* FROM approval_rule r
WHERE r.document_type = $1
AND (r.department_id = $2 OR r.department_id IS NULL)
AND $3 >= r.amount_min
AND ($3 < r.amount_max OR r.amount_max IS NULL)
AND r.is_active
AND CURRENT_DATE BETWEEN r.valid_from
AND COALESCE(r.valid_until, CURRENT_DATE)
ORDER BY (r.department_id IS NULL), r.priority
LIMIT 1;Query resolusi berjalan sekali, saat submission. Momen itu penting: engine men-snapshot step hasil resolusi ke tabel approval_instance yang menempel ke dokumen. Dokumen lalu menjalani salinan rantainya sendiri yang dibekukan. Kalau admin mengedit matrix besok, dokumen yang sedang berjalan tidak tersentuh — hanya submission baru yang melihat rule baru.
Pro tip: snapshot-saat-submission adalah perilaku tunggal terpenting yang harus benar. Alternatifnya — me-resolve ulang rule di setiap klik approve — berarti editan admin bisa diam-diam membelokkan dokumen yang sedang di tengah rantai, dan audit trail Anda tidak lagi bisa menjelaskan kenapa dokumen pergi ke siapa.
Edge Case yang Pasti Menemukan Anda
Lima skenario ini tidak eksotis. Semuanya muncul dalam tahun pertama ERP saya berjalan di production, dan masing-masing butuh keputusan desain eksplisit:
- Approver sekaligus pemohon — manajer yang mengajukan purchase request sendiri tidak boleh self-approve. Lompat ke step berikutnya atau eskalasi satu level ke atas; pilih satu kebijakan dan tegakkan di engine.
- Approver berhalangan — orang cuti. Anda butuh delegasi dengan tanggal kedaluwarsa, diatur approver atau admin, dan audit trail harus mencatat bertindak-sebagai-delegasi, bukan impersonasi.
- Rule tumpang tindih — dua rule cocok dengan dokumen yang sama. Pengurutan deterministik (spesifisitas, lalu priority, lalu id terkecil) plus peringatan di layar admin saat rule baru tumpang tindih dengan yang ada.
- Tidak ada rule yang cocok — kombinasi jenis dokumen dan nominal yang tidak dikonfigurasi siapa pun. Fail closed: blokir submission dengan pesan jelas, dan beri alert ke admin. Auto-approve karena celah konfigurasi adalah asal mula pengeluaran tak terotorisasi.
- Nominal diedit di tengah jalan — invoice yang dikoreksi naik mungkin kini masuk band lebih tinggi. Kebijakan amannya: perubahan nominal apa pun membatalkan approval berjalan dan me-resolve ulang dari step satu.
Apa pun keputusan Anda untuk tiap edge case, tuliskan ke engine sebagai kode dan ke SOP klien sebagai satu kalimat per kasus. Mode kegagalan yang mahal bukan memilih kebijakan yang salah — melainkan dua layar yang diam-diam mengimplementasikan kebijakan berbeda.
Rollout Matrix Tanpa Kekacauan
Memigrasikan ERP yang sudah live dari rantai hardcoded ke matrix adalah migrasi data plus perubahan perilaku, dan urutannya penting:
- Transkripsikan dulu perilaku hardcoded saat ini menjadi baris rule, apa adanya — approver sama, threshold sama. Rilis engine yang menjalankan kebijakan persis seperti sebelumnya.
- Jalankan resolusi ganda di staging: untuk dua minggu dokumen nyata, catat keputusan kode lama dan engine baru, lalu diff output-nya sampai nol.
- Cutover dengan scope feature flag: satu jenis dokumen dulu (expense paling rendah risikonya), lalu PO, lalu invoice AP.
- Serahkan layar admin ke finance hanya setelah cutover stabil, dengan perubahan rule butuh admin kedua untuk aktivasi — four-eyes pada matrix-nya sendiri.
- Jadwalkan review matrix kuartalan bersama finance. Rule basi yang menunjuk karyawan yang sudah resign adalah dead code versi matrix.
Intinya
Approval matrix adalah pembeda antara ERP yang dioperasikan klien dan ERP yang ditunggui developer. Rule sebagai data, snapshot saat submission, resolusi fail-closed, dan delegasi dengan kedaluwarsa — empat keputusan itu mencakup sembilan puluh persen permintaan finance, dan mengubah gejolak struktur organisasi dari deployment menjadi edit form.
Kalau Anda memulai build NestJS hari ini: tahan godaan if-statement. Skema dua tabel di atas berbiaya mungkin seminggu termasuk layar admin. Versi hardcoded juga berbiaya seminggu — hanya saja dicicil di setiap reorganisasi sepanjang umur sistem.

