Sinergi Group: Manajemen Proyek Konstruksi dengan Pengiriman WhatsApp

Sinergi Group: Manajemen Proyek Konstruksi dengan Pengiriman WhatsApp

Daftar Isi

Pekerjaan konstruksi di Indonesia dikoordinasikan lewat WhatsApp. Bukan email, bukan SaaS manajemen proyek. WhatsApp.

Jadi ketika Sinergi Group membutuhkan sistem untuk menjalankan proyek konstruksi mereka, persoalan menariknya bukan CRUD-nya, melainkan bagaimana membuat notifikasi tiba dengan andal melalui kanal yang sejak awal tidak dirancang untuk dikirimi pesan oleh server.

Ini adalah sisi internal dari aplikasi Laravel yang sama, yang situs publiknya saya tuliskan terpisah.

Domainnya

Sebuah proyek di sini bukan sekadar tugas dengan tenggat. Ia memuat dokumen yang memang dihasilkan pekerjaan konstruksi:

  • Bill of quantity — rincian material dan pekerjaan beserta harganya — disimpan sebagai berkas sekaligus nilai
  • Surat perintah kerja (SPO), juga sebagai berkas dan nilai
  • Izin awal beserta tanggal pembukaannya
  • Lokasi, mandor yang ditugaskan, dan target tanggal penyelesaian yang berubah merah begitu terlewat
  • Status yang bergerak melalui scheduledin_progressfinish
  • Progres penagihan, dicatat terpisah dari progres pekerjaan, karena pada proyek konstruksi keduanya jarang bergerak bersamaan
  • Catatan bebas, serta created_by / updated_by karena pada sistem internal bersama akhirnya akan ada yang menanyakan siapa yang mengubah suatu angka

Daftar proyeknya dibangun mengikuti cara kantor benar-benar mengajukan pertanyaan: filter berdasarkan status, mandor, status penagihan, deadline, atau ada tidaknya dokumen terlampir, pencarian pada judul dan nomor, pemilihan kolom sendiri, lalu ekspor hasilnya ke Excel. Tidak ada yang meminta “laporan” secara abstrak — yang mereka inginkan adalah pekerjaan yang terlewat tenggat pada kuartal ini untuk satu mandor, dalam bentuk spreadsheet.

Di sekelilingnya terdapat catatan pelanggaran, pengguna beserta role-nya, serta entri layanan dan portofolio yang mengisi situs publiknya — admin yang sama mengelola kedua sisi, dan itulah alasan praktis keduanya berada dalam satu basis kode.

Hak akses berjalan di atas spatie/laravel-permission, dengan Gate::before yang memberikan Super Admin seluruh kewenangan agar tidak ada yang terkunci dari sistemnya sendiri.

Bagian itulah yang memakan waktu. Bukan bagian yang layak dituliskan.

Persoalan WhatsApp

WhatsApp tidak menyediakan API resmi yang bisa langsung dipakai untuk kebutuhan ini. Yang tersedia adalah sejumlah library yang berperan sebagai perangkat pendamping tertaut — mekanisme yang sama dengan WhatsApp Web.

Dua hal mengikuti dari kenyataan itu, dan keduanya membentuk desainnya.

Sesi bisa dicabut. WhatsApp membatalkan sesi perangkat pendamping secara berkala, dengan alasan yang tidak pernah dijelaskan. Ketika itu terjadi, pengiriman berhenti sampai seseorang memasangkan ulang dengan memindai kode QR.

Library menentukan bahasanya. Pilihan yang paling terpelihara yang saya temukan, whatsapp-rust, adalah library Rust tanpa antarmuka jaringan. Ia hanya bisa dipakai di dalam proses. Itulah sebabnya sebagian sistem ini ditulis dengan Rust: bukan karena selera, melainkan karena ekosistemnya yang memutuskan.

Percobaan pertama, dan mengapa ia ditinggalkan

Versi pertama berupa worker Go yang mem-polling endpoint POST /send yang hanya terbuka di localhost. Ia berfungsi. Sekarang sudah dipensiunkan.

Alasannya adalah kode QR itu. Memulihkan sesi yang dicabut berarti masuk ke server lewat SSH lalu membaca QR pemasangan dari journalctl. Itu tidak masalah bila hanya saya operatornya, tetapi tidak berguna ketika orang yang pertama menyadari masalahnya berada di kantor, bukan di terminal.

Penggantinya

sinergi-wa-bridge adalah layanan Rust kecil yang menanamkan whatsapp-rust, menjaga sesi tetap hidup, mengelola pemasangan, dan menyediakan API HTTP. Laravel memanggilnya lewat HTTPS dengan bearer token.

Tujuan menempatkan API di depannya adalah login ulang secara mandiri. Halaman WhatsApp di dalam aplikasi, yang dibatasi permission tersendiri, dirancang untuk orang yang tidak akan membuka terminal:

  • Status sesi terkini sebagai badge, beserta nama perangkat tertaut, nomor, sejak kapan tersambung, dan kapan ter-logout
  • QR pemasangan yang dirender di browser dan menyegarkan dirinya setiap sepuluh detik, sebagaimana WhatsApp Web
  • Alternatif lewat nomor telepon — masukkan nomor dalam format internasional lalu minta kode pemasangan, untuk saat-saat rekan kerja tidak bisa memindai layar
  • Jumlah antrean notifikasi secara langsung menurut statusnya: menunggu, diproses, terkirim, gagal

Controller-nya mem-proxy setiap panggilan ke bridge, sehingga token bridge tidak pernah sampai ke browser.

Tangkapan layar pada galeri di atas adalah halaman tersebut saat proses pemasangan: belum ada perangkat tertaut, QR menunggu dipindai, dan delapan notifikasi sudah terkirim.

Membuat pengiriman bertahan atas kegagalannya sendiri

Ada tiga sifat yang saya inginkan, dan tidak satu pun datang gratis.

Tidak ada yang hilang saat queue mati. WaNotificationService::queue() menuliskan baris wa_notifications lebih dahulu, baru kemudian mengirim job-nya. Jika worker queue mati, niat untuk mengirim notifikasi tetap tercatat di basis data. Perintah php artisan wa:dispatch-pending memasukkan kembali apa pun yang tertinggal — termasuk baris yang tersangkut di tengah jalan pada status processing, yang justru kondisi yang benar-benar Anda temukan setelah crash.

Retry membedakan “belum” dari “tidak”. Hanya kegagalan sementara yang diulang: not_connected, unreachable, starting. Penerima yang ditolak — nomor salah, tidak terdaftar di WhatsApp — gagal pada percobaan pertama alih-alih dicoba lima kali sepanjang enam jam.

Backoff-nya disesuaikan dengan kegagalan nyatanya. 1 menit, 5, 15, 1 jam, 6 jam. Langkah terakhir itu bukan angka sembarangan: ia cukup panjang bagi seseorang untuk menyadari sesi yang keluar, menemukan halaman QR, lalu memasangkan ulang. Mengulang setiap 30 detik selama satu jam hanya akan menghabiskan percobaan sebelum manusia punya kesempatan bertindak.

Dan karena queue yang berhenti tampak seperti tidak terjadi apa-apa dari luar, /app/whatsapp memberi peringatan ketika notifikasi terlama yang belum terkirim melewati sepuluh menit. Kesunyian adalah mode kegagalan yang paling mahal.

Infrastruktur

Server-nya disiapkan oleh repository Ansible terpisah — role, inventory, group vars — sehingga mesinnya dideskripsikan dalam kode alih-alih dalam apa pun yang saya ingat pernah lakukan secara manual.

Deployment memakai GitHub Actions saat push, dengan development dan main dipetakan ke dua environment.

Satu hal yang sengaja tidak dilakukan: proses deploy tidak memasang cron scheduler. Itu dikonfigurasi di sisi server, di luar repository. Versi sebelumnya memasangnya dari CI, yang berarti sebuah deploy bisa mengubah jadwal tanpa disadari. Saya menghapusnya lalu meninggalkan catatan agar tidak ditambahkan kembali.

Yang perlu saya sampaikan terus terang

Bridge Rust itu kecil dan masih muda. Ia mengerjakan satu tugas — menjaga sesi, mengirim pesan, menyediakan pemasangan — dan saya sengaja mempertahankannya seperti itu, karena makin sedikit yang ia kerjakan, makin jarang saya perlu menyentuh basis kode dalam bahasa yang kefasihannya masih saya bangun.

Saya juga akan mengatakan bahwa pekerjaan soal keandalan lebih bernilai daripada pekerjaan fiturnya. Notifikasi yang biasanya tiba lebih buruk daripada tidak ada notifikasi, karena orang berhenti memeriksanya dan tidak seorang pun memberi tahu Anda bahwa mereka berhenti.

Butuh yang serupa? Hubungi saya — cukup sesuai, lewat WhatsApp.

call to action

Siap membangun proyek bersama saya?

Saya siap membantu membangun, mengembangkan, dan meluncurkan proyek Anda berikutnya — kirim pesan sekarang dan ayo kita mulai!

Mulai Sekarang