Procurement di ERP: PR ke PO ke Penerimaan ke 3-Way Match

Foto oleh cottonbro studio

Foto oleh cottonbro studio
Procurement adalah tempat ERP membayar dirinya sendiri dalam uang tunai. Modul lain kebanyakan merapikan informasi; rantai procure-to-pay adalah yang memutuskan apakah uang keluar perusahaan untuk barang yang benar-benar tiba dengan harga yang benar-benar disepakati. Saat saya mendesain alur pembelian berdampingan dengan modul AP yang saya bangun untuk ANCoraPRO di NestJS dan PostgreSQL, proses klien sebelumnya adalah pesan WhatsApp ke supplier dan satu baris Excel — dan kira-kira dua kali setahun, ada invoice terbayar untuk barang yang tidak ditemukan siapa pun di gudang.
Solusinya adalah rantai dokumen dengan empat mata rantai — purchase requisition, purchase order, goods receipt, invoice vendor — dan mesin pencocokan yang menolak melepas pembayaran sampai cerita keempatnya konsisten. Artikel ini menyusuri rantai itu satu per satu, dengan keputusan skema dan edge case yang membedakan modul procurement yang bekerja dari yang diakali.
Insight desain intinya: tiap dokumen di rantai menjawab pertanyaan berbeda, dan harus tetap menjadi record terpisah bahkan saat satu orang menangani semuanya. Requisition mencatat niat, purchase order mencatat komitmen, goods receipt mencatat realita fisik, dan invoice mencatat klaim vendor. Gabungkan dua di antaranya menjadi satu record dan Anda kehilangan kemampuan mendeteksi ketidaksesuaiannya — padahal itu seluruh intinya:
The procure-to-pay document chain
PR ----------> PO ----------> GR ----------> AP Invoice
(request) (commitment) (reality) (claim)
PR : "we need 20 office chairs" -- internal intent
PO : "we order 20 chairs @ 850,000" -- legal commitment
GR : "18 chairs arrived, 2 damaged" -- physical truth
INV: "vendor bills 20 chairs" -- payment claim
3-way match = PO vs GR vs INV
-> pay for 18, dispute 2, never pay for what never arrivedSetiap baris di setiap dokumen hilir membawa foreign key ke baris hulunya: baris PO mereferensi baris PR, baris GR mereferensi baris PO, baris invoice mereferensi baris PO. Keterkaitan level baris itulah, bukan level dokumen, yang membuat pengiriman parsial dan invoice terpecah bisa ditangani nanti.
PR ada supaya meminta sesuatu itu mudah dan mengikat uang itu terkontrol. Karyawan mana pun bisa mengajukan: deskripsi bebas boleh, item katalog lebih disukai, estimasi harga terisi otomatis dari pembelian terakhir. Friksi di sini mendorong orang kembali ke WhatsApp — form PR harus selesai di bawah dua menit, termasuk di mobile, karena orang yang sadar forklift butuh sparepart sedang berdiri di sebelah forkliftnya.
Approval adalah tempat kebijakan pengeluaran hidup, dirutekan berdasarkan band nominal dan departemen lewat approval matrix yang configurable, bukan rantai hardcoded. Satu keputusan yang layak dieksplisitkan: approval PR mengotorisasi kebutuhannya, bukan supplier atau harga finalnya. Purchasing yang mengonversi PR yang disetujui menjadi PO, memilih atau menegosiasikan vendor — memisahkan siapa boleh meminta dari siapa boleh mengikat adalah kontrol fraud dasar.
Pro tip: agregasikan baris PR yang disetujui per item lintas departemen sebelum dikonversi ke PO. Tiga departemen yang masing-masing memesan sepuluh dus kertas adalah satu PO untuk tiga puluh dengan harga lebih baik — dan layar konsolidasi ini konsisten menjadi tempat pertama modul procurement menunjukkan penghematan terukur.
PO biasanya komitmen yang bermakna hukum ke vendor, jadi lifecycle-nya perlu ketat dengan cara yang tidak dibutuhkan PR. Aturan yang saya implementasikan:
GR adalah gudang mengonfirmasi apa yang fisik tiba: kuantitas dihitung, kondisi dicatat, diposting terhadap baris PO spesifik. Ia mengerjakan dua tugas sekaligus — menambahkan stock movement ke inventory, dan menciptakan bukti kuantitas-diterima yang akan dibandingkan mesin match dengan invoice. Pengiriman parsial itu normal, jadi banyak GR per baris PO harus jadi warga kelas satu, dengan total diterima berjalan dipelihara per baris.
Desain layar GR untuk dok penerimaan, bukan untuk accounting: field kuantitas besar, baris PO sudah terdaftar sehingga penerima mengonfirmasi alih-alih mengetik, field kuantitas-rusak yang otomatis membuat draft retur vendor. Kualitas seluruh match hilir Anda bergantung pada pekerja gudang yang menemukan layar ini lebih cepat daripada formulir kertas.
Jangan pernah izinkan penerimaan tanpa PO sebagai jalur rutin. Keadaan darurat memang terjadi — kiriman mendesak sungguhan dengan dokumen menyusul — jadi sediakan penerimaan eksepsi yang butuh approver bernama dan membuat PO retroaktif dalam tenggat. Kalau penerimaan tanpa referensi bebas friksi, seluruh rantai terurai dalam hitungan bulan, karena setiap penerimaan menjadi eksepsi.
Three-way matching membandingkan PO, akumulasi goods receipt, dan invoice vendor sebelum pembayaran disetujui — kontrol standar untuk menangkap penagihan berlebih, pengiriman kurang, dan fraud. Tiga pemeriksaannya, apa yang ditangkap masing-masing, dan di mana toleransi berada:
| Pemeriksaan | Membandingkan | Menangkap | Toleransi |
|---|---|---|---|
| Kuantitas | Qty ditagih vs qty diterima, per baris PO | Penagihan barang yang belum terkirim; tagihan ganda lintas invoice terpecah | Nol — kuantitas bisa dihitung; jangan pernah bayar lebih dari yang tiba |
| Harga | Harga satuan invoice vs harga satuan PO | Harga merayap naik, price list salah, selip mata uang | Band persentase kecil (0,5 sampai 2 persen) menyerap rounding; di atasnya antre untuk review |
| Eksistensi | Baris invoice vs baris PO terbuka yang disetujui | Invoice untuk barang yang tidak pernah dipesan — vektor fraud klasik | Tidak ada — tanpa referensi PO, tidak ada jalur otomatis ke pembayaran |
Jalankan match per baris invoice saat posting dan simpan hasilnya sebagai status di baris itu. Baris yang cocok mengalir ke penjadwalan pembayaran tanpa disentuh tangan manusia; eksepsi mendarat di antrean yang menampilkan ketiga dokumen berdampingan dengan selisihnya disorot. Metrik yang diawasi adalah touchless rate — porsi baris invoice yang cocok otomatis. Setup yang matang mencapai jauh di atas separuh, dan setiap jenis eksepsi yang berulang menunjuk perbaikan hulu: penerima melewatkan GR, price list kedaluwarsa, vendor berimprovisasi unit of measure.
// NestJS: match result computed per invoice line, never per document
type MatchStatus = 'MATCHED' | 'PRICE_VARIANCE' | 'QTY_OVER_BILLED'
| 'NOT_RECEIVED';
function matchInvoiceLine(
poLine: PoLine,
receivedQty: number, // SUM of GR lines for this PO line
invLine: InvoiceLine,
tolerancePct: number, // e.g. 1.0 = 1% price tolerance
): MatchStatus {
if (receivedQty === 0) return 'NOT_RECEIVED';
if (invLine.qty > receivedQty) return 'QTY_OVER_BILLED';
const priceDeltaPct =
Math.abs(invLine.unitPrice - poLine.unitPrice)
/ poLine.unitPrice * 100;
if (priceDeltaPct > tolerancePct) return 'PRICE_VARIANCE';
return 'MATCHED';
}
// MATCHED -> auto-release for payment scheduling
// anything else -> exception queue with PO + GR side by sideHappy path-nya kerjaan satu sore. Empat skenario ini proyek sebenarnya:
Sebelum menyebut modul procurement siap production, saya memverifikasi lima hal ini berurutan:
Modul procurement adalah empat dokumen dan satu wasit. Jaga dokumen tetap terpisah supaya bisa saling tidak setuju, tautkan di level baris supaya realita parsial muat, dan biarkan mesin match — bukan kebiasaan atau hierarki — memutuskan invoice mana yang menyentuh uang tanpa manusia. Bangun seperti ini dan sistem membayar persis untuk yang tiba; bangun lebih longgar dan Anda baru saja mendigitalkan proses WhatsApp dengan langkah ekstra.
Sumber dan bacaan lanjutan