# Alur Persetujuan Multi-Level Accounts Payable dengan NestJS

> Cara saya membangun sistem persetujuan AP multi-level yang dapat dikonfigurasi berdasarkan jumlah dengan NestJS dan PostgreSQL—dari desain schema hingga algoritma rantai persetujuan.

URL: https://www.matthewswong.com/id/blog/accounts-payable-multi-approval-nestjs/

- Author: Matthews Wong (https://www.matthewswong.com/id/about/)
- Published: 2025-06-10
- Updated: 2025-06-10
- Category: ERP

Dalam pekerjaan konsultasi ERP lepas yang saya jalani, saya membangun modul accounts payable custom yang menangani pemrosesan faktur vendor. Alur persetujuan adalah bagian yang menarik—approver yang berbeda diperlukan tergantung seberapa besar fakturnya, dan rantai persetujuan perlu dapat dikonfigurasi oleh keuangan tanpa deployment kode.

## Persyaratan Bisnis

Persyaratan tim keuangan: faktur di bawah 5 juta IDR membutuhkan satu approver (supervisor), faktur antara 5 dan 50 juta membutuhkan dua approver (supervisor lalu manajer), dan faktur di atas 50 juta membutuhkan tiga approver. Ambang batas harus dapat disesuaikan tanpa perubahan kode.

### Desain Schema: Kebijakan yang Dapat Dikonfigurasi

Keputusan desain utama adalah menyimpan ambang batas persetujuan di tabel database (approval_policies) daripada hardcode. Setiap baris mewakili satu level: nomor level, jumlah minimum, jumlah maksimum opsional, dan peran yang diperlukan.

### Tabel Faktur dan Step

Tabel ap_invoices menyimpan header faktur. Kolom current_level melacak level persetujuan yang saat ini aktif. Tabel ap_approval_steps menyimpan satu baris per level per faktur. Ketika step disetujui, service menambah current_level dan membuat notifikasi untuk approver berikutnya.

> Simpan rantai persetujuan sebagai baris database individual, bukan sebagai array JSON pada faktur. Ini memberi Anda audit trail lengkap dengan timestamp dan memudahkan query tanpa parsing JSON.

## Membangun Rantai Persetujuan

Ketika faktur diajukan, fungsi buildApprovalChain memquery tabel approval_policies untuk menemukan semua level yang berlaku. Jika tidak ada karyawan aktif dengan peran yang diperlukan, fungsi melempar error dan faktur ditahan dalam status DRAFT.

### Logika Pemrosesan Berurutan

Fungsi advance menangani perkembangan langkah demi langkah. Ketika approver menyetujui step mereka, service memeriksa apakah ada step tambahan dengan level lebih tinggi. Jika ya, increment current_level dan kirim notifikasi ke approver berikutnya. Jika tidak ada, tandai faktur sebagai FULLY_APPROVED.

> **Penugasan Approver saat Pengajuan**

> Rantai persetujuan menetapkan ID approver spesifik pada saat pengajuan faktur, bukan secara dinamis. Jika manajer berubah setelah supervisor menyetujui, manajer asli masih mendapat notifikasi. Bangun admin tool untuk menugaskan ulang step ketika perubahan organisasi terjadi.

## Audit Trail dan Imutabilitas

Setiap tindakan persetujuan dicatat dengan timestamp dan catatan opsional di tabel ap_approval_steps. Rekaman tidak pernah dihapus atau ditimpa—faktur yang ditolak tetap ada dengan riwayat persetujuan lengkapnya.

## Antarmuka Persetujuan React

Setiap approver melihat antrian faktur yang saat ini ditugaskan kepada mereka. Antrian menampilkan nama vendor, nomor faktur, jumlah, tanggal jatuh tempo, dan riwayat persetujuan sejauh ini. Tombol Setujui atau Tolak tersedia dengan kolom teks yang diperlukan untuk alasan penolakan.

## Menguji Logika Persetujuan

Kasus uji utama: faktur di bawah ambang batas (persetujuan satu step), faktur di atas ambang (multi-step berurutan), percobaan persetujuan bersamaan, penolakan di tengah jalan, dan perubahan kebijakan saat faktur sedang dalam proses.

## Sumber & Bacaan Lanjutan

- NestJS — Guards — https://docs.nestjs.com/guards
- PostgreSQL — Fungsi JSON — https://www.postgresql.org/docs/current/functions-json.html
- Martin Fowler — Approval Process Pattern — https://martinfowler.com/bliki/ApprovalProcess.html

## Source links

- https://docs.nestjs.com/guards
- https://www.postgresql.org/docs/current/functions-json.html

## FAQ

### Mengapa ambang batas persetujuan disimpan di tabel database, bukan di-hardcode dalam kode?

Menyimpan ambang batas di tabel approval_policies memungkinkan tim keuangan menyesuaikan level persetujuan—seperti batas 5 juta dan 50 juta IDR—tanpa perlu deployment kode baru. Setiap baris menyimpan nomor level, jumlah minimum, jumlah maksimum opsional, dan peran yang diperlukan. Ini membuat alur kerja dapat dikonfigurasi tanpa melibatkan tim engineering.

### Mengapa step persetujuan disimpan sebagai baris database individual, bukan sebagai array JSON pada faktur?

Baris individual di ap_approval_steps memberi setiap step timestamp dan status tersendiri, menghasilkan audit trail lengkap yang dapat direkonstruksi oleh keuangan bertahun-tahun kemudian. Pendekatan ini juga memungkinkan query SQL langsung seperti 'semua faktur yang menunggu persetujuan Direktur' tanpa parsing JSON, serta mendukung index pada (invoice_id, level, status) untuk pencarian cepat.

### Apa yang terjadi jika tidak ada karyawan aktif dengan peran yang diperlukan saat faktur diajukan?

Fungsi buildApprovalChain melempar error dan faktur ditahan dalam status DRAFT, bukan diteruskan secara diam-diam. Ini mencegah faktur terjebak dalam kondisi tidak dapat ditindaklanjuti karena tidak ada yang bisa melakukan step persetujuan berikutnya.

### Mengapa ID approver ditetapkan saat pengajuan faktur, bukan dicari secara dinamis di setiap step?

Menetapkan ID approver saat pengajuan memastikan pergantian personel di tengah proses tidak secara tak terduga mengalihkan persetujuan ke orang baru—manajer asli tetap mendapat notifikasi step berikutnya. Solusi yang disarankan untuk perubahan organisasi adalah admin tool terpisah yang memungkinkan penugasan ulang step individual tanpa mengulang seluruh rantai.

### Bagaimana sistem menangani penolakan di tengah rantai persetujuan?

Ketika salah satu approver menolak faktur, service menandai faktur sebagai REJECTED dan membatalkan semua step yang tersisa. Faktur dan stepnya tidak pernah dihapus atau ditimpa, sehingga riwayat persetujuan lengkap—termasuk alasan penolakan dan timestamp—tetap tersedia untuk keperluan audit dan kepatuhan.

---

- HTML version: https://www.matthewswong.com/id/blog/accounts-payable-multi-approval-nestjs/
- English: https://www.matthewswong.com/en/blog/accounts-payable-multi-approval-nestjs/
- Site index for agents: https://www.matthewswong.com/llms.txt
- Sitemap: https://www.matthewswong.com/sitemap.xml
- API description (OpenAPI): https://www.matthewswong.com/openapi.json
