Ringkasan Perubahan Alur
Bagian ini menjawab satu pertanyaan: alur lamanya seperti apa, dan sekarang jadi seperti apa?
Setiap halaman di bawah menyajikan tiga hal:
- Diagram alur lama
- Diagram alur baru
- Kenapa diubah, dan konsekuensi yang diterima
Sesuai permintaan, alur lama tetap didokumentasikan apa adanya. Ini penting untuk memahami data historis, membaca log lama, dan mengerti kenapa beberapa kolom/tabel ada.
Tujuh perubahan besar
| # | Perubahan | Dari | Menjadi | Detail |
|---|---|---|---|---|
| 1 | Booking | AI langsung membuat janji temu, gratis | Draft berbayar → janji temu setelah lunas | Baca |
| 2 | Konfirmasi pembayaran | Dikirim langsung dari service pembayaran | Event ReservationPaid → listener → notifier terpusat + sapuan 5 menit | Baca |
| 3 | Chat Rooms | Membaca store lokal hasil webhook | Live proxy ke ChatPanel Jasnita | Baca |
| 4 | Sinkronisasi kontak | Hanya tombol manual | Real-time via webhook + sapuan 10 menit + riwayat lintasan | Baca |
| 5 | SIMRS Sync | Satu lintasan penuh; gagal = ulang dari nol | Cursor checkpoint per chunk; dilanjutkan otomatis | Baca |
| 6 | Pemegang percakapan | Semua percakapan tampak dipegang AI; balasan agen tidak dicatat | Atribusi sent_by, inferensi pemegang, takeover eksplisit, label | Baca |
| 7 | Akun WhatsApp | Bisa dihapus | Hanya bisa diaktifkan/dinonaktifkan | Lihat di bawah |
Perubahan yang lebih kecil tapi penting
Tool allowlist: dari "tidak diiklankan" jadi ditegakkan
Kenapa mendesak: dulu create_appointment yang dihalusinasikan hanya membuat satu janji temu gratis — tidak berbahaya. Setelah booking jadi berbayar, tool yang sama menerbitkan invoice Duitku sungguhan. Model yang menyebut nama tool tak diiklankan (halusinasi, riwayat dari konfigurasi lama, atau prompt injection) berarti pasien ditagih untuk booking yang tak pernah ia minta.
Penjagaannya diletakkan di kelas yang memiliki allowlist-nya, bukan disalin ke dua pemanggil — aturan yang disalin ke dua tempat adalah persis cara celah itu lahir.
payment_url: dari tidak disimpan jadi disimpan
Bonus: link juga tersimpan seandainya balasan AI yang membawanya gagal terkirim (LLM error setelah tool berjalan, batas iterasi tool tercapai, atau gelembung pesan gagal). Tanpa salinan itu, invoice Duitku hidup sementara pasien tidak memegang apa pun — lalu menerima pemberitahuan kedaluwarsa untuk link yang tak pernah ia terima.
Penjagaan callback: dari "terminal" jadi "verdict-aware"
Prinsipnya: Expired hanyalah tebakan lokal kami, bukan fakta. Duitku yang tahu kenyataannya.
Callback Duitku: dibaca di dalam transaksi + terkunci
Dulu baris Payment dibaca di luar transaksi mana pun, sehingga dua callback bersamaan untuk satu order bisa sama-sama membaca Pending, sama-sama lolos penjagaan, dan sama-sama memancarkan ReservationPaid. Untuk booking berdraft, lockForUpdate() milik confirmPaid() masih menyelamatkannya — tapi untuk pembayaran tanpa draft (reservasi manual dari Chat Rooms), tidak ada penjagaan sama sekali: pasien menerima dua pesan dan dua baris WhatsappMessage.
Sekarang pembacaan, penjagaan, dan penulisan berada dalam satu transaksi dengan lockForUpdate().
Hapus akun WhatsApp → tombol aktif/nonaktif
Akun nonaktif memang sudah dilewati sebagai pengirim (ChatRoomService dan ChatPanelService mengambil baris is_active pertama). Penghapusan ditutup di level resource (canDelete()/canDeleteAny() mengembalikan false), bukan sekadar tombolnya dilepas — supaya aksinya tidak bisa kembali hanya dengan pemberian izin Shield.
Perubahan serupa juga diterapkan pada panel Kontak: tombol hapus dilepas.
Whitelist balasan AI untuk testing
Fitur baru, bukan penggantian: WHATSAPP_TEST_MODE sebagai saklar global, plus kolom "Whitelist Balasan AI" per akun. Saat testing, hanya nomor tester yang dibalas AI dan setiap pesan keluar dialihkan ke nomor tester — supaya pasien asli tidak pernah menerima apa pun. Detail di WhatsApp & AI.
Chat Rooms: pemuatan data ditunda
Dulu data ditarik saat render pertama sehingga halaman memblokir menunggu API Jasnita — agen melihat layar kosong beberapa detik. Sekarang kerangka halaman tampil instan lewat wire:init, data menyusul, dengan status loading dan auto-scroll ke pesan terbaru.
Kontak: ekspor Excel
Fitur baru di halaman Kontak untuk mengekspor daftar kontak ke berkas Excel.
Kolom & tabel yang lahir dari perubahan ini
Kalau Anda membaca skema database dan bertanya "kolom ini untuk apa":
| Kolom / Tabel | Lahir dari perubahan |
|---|---|
booking_requests (seluruh tabel) | Booking gratis → berbayar |
booking_requests.payment_url | Kehilangan link tidak boleh berarti tagihan kedua |
booking_requests.whatsapp_account_id | Konfirmasi harus kembali lewat akun percakapan yang sama |
booking_requests.expires_at | Sapuan kedaluwarsa |
sync_states.cursor | SIMRS Sync resumable |
contact_sync_runs (seluruh tabel) | "Sinkron otomatis" harus bisa dibedakan dari "sinkron otomatis yang diam-diam mati" |
whatsapp_business_accounts.ai_reply_whitelist | Mode testing |
whatsapp_business_accounts.gateway_* (OAuth) | Integrasi Jasnita Toschat |
contacts.assigned_user_id | Assign/Transfer di Chat Rooms |
contacts.handled_by | Takeover eksplisit — memblokir auto-reply AI |
whatsapp_messages.media_* | Kirim/terima media di Chat Rooms |
whatsapp_messages.sent_by / sent_by_user_id | Membedakan balasan AI dari balasan agen |
chat_labels + chat_label_contact | Label percakapan buatan tim |
ai_token_usages | Pencatatan pemakaian token per balasan |
ai_agents.knowledge_files* | Ekstraksi PDF/gambar jadi knowledge |
ai_agents.llm_last_usage_snapshot / llm_last_probe_snapshot | Monitoring LLM di UI |