Situlah momen ketika Frappe mulai terasa punya kekuatan sesungguhnya: bukan hanya sebagai framework, tapi sebagai ekosistem aplikasi yang bisa dipasang, dilepas, dan disesuaikan tanpa menyentuh kode sumber aplikasi aslinya. Saya akan bahas tiga hal sekaligus dalam satu alur yang berkesinambungan: cara memasang aplikasi dari ekosistem Frappe dengan CRM sebagai contoh, cara mengustomisasi DocType inti ERPNext tanpa merusak jalur upgradenya, dan cara mengekspor semua kustomisasi itu sebagai fixture supaya bisa dibawa ke staging maupun produksi. Semuanya saya tulis dari bench development yang menampung belasan aplikasi

Bagaimana aplikasi terpasang di Frappe
Ada dua perintah kunci yang membedakan konsep ini. Perintah get-app mengunduh source code aplikasi ke folder apps/ di dalam bench, tapi aplikasi belum aktif ke mana pun. Perintah install-app-lah yang mendaftarkan aplikasi ke sebuah situs: membuat tabel database untuk semua DocType milik aplikasi, menjalankan patch awal, dan membangun assetnya. Satu aplikasi yang sudah di-get bisa di-install ke banyak situs dalam bench yang sama, masing-masing dengan data berbeda.
Contoh nyata di bench saya: folder apps/ berisi frappe, erpnext, hrms, crm, dan enam custom app internal. Situs development menjalankan semuanya sekaligus. Pemahaman layer ini penting karena mayoritas masalah pemula soal aplikasi hilang atau DocType tidak ditemukan sebenarnya adalah aplikasi ter-attach ke situs yang salah. Kalau bench lokal Anda belum siap, panduan menyiapkannya pernah saya tulis di artikel cara install Frappe dan ERPNext di lokal.
Install aplikasi CRM dari Frappe Cloud marketplace
CRM adalah aplikasi buatan tim Frappe untuk mengelola lead dan deal penjualan, dan menjadi contoh pas karena gratis, aktif dikembangkan, dan berdiri di atas ERPNext. Dari dalam folder bench:
# ambil source code CRM ke folder apps/
bench get-app crm https://github.com/frappe/crm.git
# pasang ke situs (membuat tabel + sinkron asset)
bench --site erp.localhost install-app crm
Setelah proses install selesai, workspace CRM langsung muncul di sidebar desk. Buka, dan Anda mendapat aplikasi pipeline penjualan lengkap: lead, deal, organization, sampai quotation multi-section. Versi yang saya jalankan di bench menunjukkan crm 2.0.0-dev pada branch develop, dan itu hidup berdampingan dengan ERPNext v16 tanpa konflik karena keduanya satu ekosistem yang dirilis serempak.
Untuk aplikasi lain caranya sama persis: bench get-app payments untuk payment gateway, bench get-app helpdesk untuk tiket dukungan, atau bench get-app lms untuk learning management. Yang perlu dijaga hanyanya kompatibilitas branch: aplikasi versi 16 dipasang di bench Frappe 16, dan aplikasi develop di bench develop. Mencampur branch adalah sumber bug paling umum.
Mengustomisasi DocType inti ERPNext dengan benar
Sekarang bagian yang paling sering disalahpahami: menambah field ke DocType milik ERPNext. Ada dua jalur, dan pilihan jalurnya menentukan nasib upgrade Anda ke depan.
Jalur pertama adalah edit langsung DocType-nya. Buka daftar DocType, cari misalnya Sales Order, tambahkan field baru di form builder. Cara ini cepat tapi berbahaya: perubahan menempel di database situs, hilang begitu Anda migrate dengan schema aplikasi yang baru, dan tidak terbawa ke situs lain. Jalur ini hanya cocok untuk eksperimen sekali pakai.
Jalur kedua, yang benar, adalah Custom Field: buka menu Customize Form, pilih DocType tujuan, lalu tambahkan field di sana. Field yang dibuat lewat jalur ini disimpan sebagai record Custom Field, terpisah dari definisi DocType asli, sehingga upgrade ERPNext tidak akan menimpanya. Fieldnya otomatis diberi prefiks custom_ dan hidup persis seperti field native: bisa masuk form view, list view, validasi, sampai report.
Di bench saya, custom app CRM menambahkan seratus enam puluh empat Custom Field ke berbagai DocType inti seperti CRM Deal, CRM Lead, dan CRM Organization, mulai dari field sederhana sampai field tabel anak. Semuanya lewat Customize Form atau via API Custom Field, tidak satu pun dengan mengedit DocType asli. Hasilnya aplikasi CRM upstream tetap bisa di-update kapan saja.
Yang setara pentingnya dengan Custom Field adalah Property Setter: kustomisasi properti field yang sudah ada, misalnya membuat field jadi mandatory, mengubah label, menyembunyikan field dari form, atau mengganti opsi Select. Semua perubahan properti semacam ini juga harus lewat Customize Form, bukan edit DocType, dengan alasan yang sama: agar tidak hilang saat upgrade.
Level kustomisasi berikutnya: custom app sendiri
Saat kustomisasi Anda tumbuh, saatnya menaikkannya ke custom app: aplikasi kosong milik tim Anda yang menjadi wadah semua DocType baru, script, dan fixture kustomisasi. Membuatnya:
bench new-app internal_custom
# isi prompt: app title, nama modul, email maintainer
bench --site erp.localhost install-app internal_custom
Custom app memberi Anda tiga kemampuan yang tidak dimiliki Customize Form. Pertama, DocType baru milik sendiri: di bench saya ada app master yang menampung puluhan master data bisnis seperti Movement Type, Seaport, Branch, sampai Carrier Mapping, semuanya DocType baru dengan module sendiri, bukan tambahan pada DocType ERPNext. Kedua, override perilaku: file python di custom app bisa meng-override controller DocType mana pun milik aplikasi lain. Ketiga, wadah fixture: semua kustomisasi yang menempel ke aplikasi inti diekspor ke sini, dan custom app inilah yang di-push ke git.
Fixture: mengekspor kustomisasi agar portabel
Fixture adalah mekanisme Frappe untuk mengekspor dokumen konfigurasi dari database menjadi file JSON di dalam aplikasi, sehingga ikut version control dan bisa dimigrasikan ke situs lain lewat bench migrate. Inilah jembatan antara duduk di database dan hidup di git.
Konfigurasinya ada di hooks.py aplikasi. Bentuk paling sederhana cukup menyebut nama DocType, dan bentuk dengan filter membatasi dokumen mana saja yang diekspor. Ini pola yang saya pakai di custom app CRM:
fixtures = [
# semua dokumen dari DocType ini
"CRM Fields Layout",
# Custom Field untuk DocType tertentu saja
{
"dt": "Custom Field",
"filters": [["dt", "in", ["CRM Deal", "CRM Lead", "CRM Organization"]]],
},
# Property Setter untuk DocType yang sama
{
"dt": "Property Setter",
"filters": [["doc_type", "in", ["CRM Deal", "CRM Lead"]]],
},
# Client/Server Script milik modul custom saja
{
"dt": "Client Script",
"filters": [["module", "=", "CRM Custom"]],
},
{
"dt": "Server Script",
"filters": [["module", "=", "CRM Custom"]],
},
# Print Format, Email Template, sampai Workspace
{
"dt": "Print Format",
"filters": [["module", "=", "CRM Custom"]],
},
{
"dt": "Email Template",
"filters": [["reference_doctype", "in", ["CRM Lead", "CRM Deal", "Quotation"]]],
},
]
Perhatikan polanya: Custom Field difilter berdasarkan dt karena kita hanya ingin membawa kustomisasi milik DocType yang kita sentuh, bukan seluruh bench. Client Script difilter berdasarkan module supaya script milik aplikasi lain tidak ikut tersedot. Filter adalah pembeda antara fixture yang rapi dan fixture yang menjadi kantong kresek berisi konfigurasi asing.
Ekspornya sendiri satu perintah:
bench --site erp.localhost export-fixtures
Hasilnya berupa file JSON per DocType di folder fixtures aplikasi: custom_field.json, property_setter.json, client_script.json, dan seterusnya, lengkap dengan seluruh definisi field dan propertinya. File inilah yang di-commit ke git. Di situs tujuan, cukup jalankan bench –site nama-situs migrate dan Frappe akan mengimpor semua fixture itu secara idempoten: dokumen yang sama di-update, yang belum ada dibuat.
Jebakan fixture yang saya pelajari dengan susah payah
Pertama, filter module yang terlalu percaya diri. Di bench yang sudah lama hidup, banyak record Custom Field punya kolom module yang kosong, jadi filter module saja akan menjatuhkan kustomisasi sah yang tidak sengaja tidak ber-module. Solusinya filter berdasarkan dt yang eksplisit, seperti contoh di atas.
Kedua, DocType dengan autoname autoincrement tidak boleh jadi fixture. Saya pernah memasukkan CRM View Settings ke fixture dan mendapati migrate berikutnya membuat duplikat baris terus menerus, satu setiap kali migrate jalan, karena fixture import selalu insert sedangkan autoincrement tidak punya nama stabil untuk di-match. Perbaikannya: keluarkan dari fixtures, lalu sync lewat after_migrate hook di hooks.py yang memakai upsert manual. Komentar di hooks saya masih menyimpan catatan ini sampai sekarang sebagai pengingat.
Ketiga, urutan ekspor. Fixture diimpor sesuai urutan daftar, jadi kalau Custom Field Anda dirujuk oleh Property Setter atau Client Script, pastikan Custom Field ada lebih dulu di daftar. Keempat, jangan pernah mengedit file fixture secara manual lalu berharap sync balik; fixture adalah hasil ekspor satu arah dari database. Alurnya selalu: ubah di situs, export-fixtures, commit.
Alur kerja lengkap dari kustomisasi sampai produksi
Dirangkum jadi siklus yang saya jalankan setiap minggu. Di situs development, lakukan kustomisasi apa pun lewat Customize Form atau DocType milik custom app. Setelah satu batch perubahan selesai, jalankan export-fixtures, periksa diff di git dengan teliti, lalu commit. Push custom app ke repository.
Di situs staging, git pull custom app-nya lalu bench migrate: fixture terimpor, kustomisasi ikut. Uji di staging, dan kalau lolos, ulangi langkah yang sama di produksi. Seluruh perjalanan kustomisasi dari laptop Anda sampai server produksi hanyalah git push, git pull, dan bench migrate. Tidak ada copy database manual, tidak ada export-import spreadsheet, dan yang paling penting: tidak ada satu baris pun perubahan yang menggantung hanya di kepala satu orang.
Kalau sebelumnya Anda membangun kustomisasi langsung di produksi, migrasikan ke pola fixture sesegera mungkin. Situs yang kustomisasinya hanya hidup di database adalah situs yang tidak bisa di-upgrade dengan aman, dan fixture adalah satu-satunya jalan membuatnya bernapas lega lagi.