Lewati ke isi

10 โ€” Arsitektur Klien, Autentikasi & Realtime

Dibaca oleh: Backend & developer klien (web/Flutter/desktop). Tujuan: menyatukan rencana fitur baru (AI, chat, notifikasi realtime) dan kenyataan klien sekarang (Delphi/DevExpress) ke dalam satu arah arsitektur yang konsisten dengan keputusan yang sudah dibuat (PostgreSQL trigger-first, satu backend API, 1 database erp dengan schema-per-tenant).

Lahir dari konsultasi 2026-05-17. Statusnya rencana/arah yang direkomendasikan; difinalkan saat pemilik setuju.


1. Peta Besar

            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
            โ”‚              Firebase (layanan)              โ”‚
            โ”‚  Auth (Google/email/MFA)   FCM (push Android)โ”‚
            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                    โ”‚ verifikasi token          โ”‚ kirim notif
  Web (baru) โ”€โ”     โ–ผ                           โ–ฒ
  Flutter   โ”€โ”€โ”ผโ”€โ–บ  Backend API (Node/TS) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  Delphi*   โ”€โ”€โ”˜     โ”‚  authZ: pusat.pengguna     โ”‚ LISTEN
                    โ–ผ                            โ”‚
            PostgreSQL erp: pusat + tenant schema (trigger-first)
                    โ”‚  pg_notify saat event penting โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
* Delphi = transisi, akses DB langsung apa adanya, lalu DIPENSIUNKAN (ยง5)

Prinsip pembatas (penting, sejalan Dokumen 03 ยง7 soal JSONB):

PostgreSQL = sistem pencatat (akuntansi, stok, transaksi). Firebase = layanan komunikasi & identitas (auth, push, chat). Jangan pernah menaruh data keuangan di Firebase. Jangan menaruh chat/notifikasi volatil sebagai beban utama di Postgres.


2. Autentikasi โ€” Firebase Auth (rencana pemilik)

Keputusan terkonfirmasi 2026-05-17: proyek Firebase autentikasi = SATU proyek luckyjayagroup (Project ID: luckyjayagroup) untuk SEMUA tenant & klien. Otorisasi per-tenant tetap di pusat.pengguna_tenant โ€” bukan proyek Firebase per tenant. Proyek Firebase lain yang ada di akun (luckyjayagroupleontech/leontech, lele, commands-center) TIDAK dipakai sebagai sumber auth sistem ini. Status: CLI & login siap (knavinkids@gmail.com); workspace belum di-firebase init, Service Account Admin SDK belum dibuat (lihat docs/TODO.md ยงC & backend/TODO.md).

Pemilik memakai Google Auth / Firebase Auth. Ini cocok dan direkomendasikan, dengan pembagian peran yang tegas:

Lapis Tugas Di mana
Autentikasi Buktikan "siapa kamu": password, Google sign-in, MFA, reset password Firebase Auth
Otorisasi "Boleh akses tenant & peran apa" pusat.pengguna + pusat.pengguna_tenant (Dokumen 04/08)

Alur (ringkas; detail di frontend/flutter/erp/AUTH.md):

  1. Klien login via Firebase โ†’ dapat Firebase ID token.
  2. Backend verifikasi token (Firebase Admin SDK) โ†’ POST /api/auth/sesi (HTTP 200 selalu).
  3. Jika firebase_uid ada di pusat.pengguna โ†’ terdaftar: true + JWT aplikasi. Jika tidak ada โ†’ terdaftar: false + access_token: null โ†’ klien tampilkan DashboardUmumPage (bukan error 401). Siapapun bisa login Google.
  4. Pilih tenant โ†’ JWT berisi tenant_kode + peran (FK ke pusat.tipe_pengguna)
  5. kontak_id lokal โ†’ routing ke pool/schema tenant.
  6. Response pilih tenant juga membawa context akses yang disimpan lokal: akses_lokasi[] dan akses_kas[].

2.1 Context Lokal Setelah Pilih Tenant

Setelah tenant dipilih, secure storage lokal klien minimal harus menyimpan:

  • tenant_kode
  • peran
  • kontak_id_lokal
  • akses_lokasi[]
  • akses_kas[]

Fungsi data ini:

  • default filter UI
  • membatasi opsi lokasi/akun kas yang ditampilkan
  • menjaga UX tetap konsisten antar screen

Tetapi keamanan akses tetap milik backend. Klien tidak boleh dianggap sebagai sumber kebenaran untuk otorisasi data.

2.2 Mapping Canonical Peran dari Legacy inet.tipe

Mapping yang dipakai sistem sekarang:

inet.tipe inet_tipe.nama tipe_pengguna.kode
0 None none
1 Sales sales
2 Gudang gudang
3 Admin admin
4 Direksi direksi
5 Customer customer
6 Umum umum

Keuntungan langsung:

  • Hapus utang keamanan nyata: inet lama simpan password plaintext + dynamic SQL rawan injeksi (temuan Dokumen 08). Dengan Firebase, password tak pernah lagi ada di DB kita.
  • Satu identitas untuk semua klien (web/Flutter/desktop-baru), menggantikan dua pintu lama (operator desktop + inet mobile).
  • Fitur berat (MFA, reset, Google) gratis & terkelola โ€” tak perlu dibangun.

Catatan: pusat.pengguna tidak menyimpan password (lihat skema Dokumen 04 ยง3, kolom firebase_uid). Saat onboarding, user lama dipetakan ke akun Firebase lewat email.


3. Notifikasi Realtime (Android kuat) โ€” FCM + LISTEN/NOTIFY

Implementasi Flutter: frontend/flutter/erp/NOTIFIKASI-FCM.md

Pemilik ingin notifikasi realtime yang kuat untuk Android. Rekomendasi: Firebase Cloud Messaging (FCM) โ€” pasangan alami Firebase Auth, terbaik untuk push Android (jalan walau app background/tertutup, andal).

Bagaimana ia menyatu dengan trigger-first (logika di DB)?

Event penting terjadi (mis. transaksi disetujui, stok kritis)
  โ””โ”€ trigger / fungsi PL/pgSQL  โ†’  pg_notify('kanal_notif', payload JSON)
        โ””โ”€ Backend LISTEN 'kanal_notif'
              โ””โ”€ kirim push via FCM ke device token user terkait
  • DB tetap jadi sumber peristiwa (konsisten dengan filosofi trigger-first): trigger memutuskan "ini layak dinotifikasi", pg_notify mengirim sinyalnya.
  • Backend = relai: dari LISTEN Postgres โ†’ FCM.
  • Device token FCM per pengguna disimpan di pusat (mis. pusat.perangkat(pengguna_id, fcm_token, platform, terakhir_aktif)).
  • Jangan memanggil FCM langsung dari trigger (jangan ada HTTP di dalam DB). Trigger hanya pg_notify; backend yang bicara ke dunia luar.

3.1 Keputusan Proyek Saat Ini

Untuk sistem ERP ini, notifikasi user dipakukan ke FCM-only. WebSocket/SSE tidak dipakai sebagai jalur notifikasi operasional.

Alasan:

  • push tetap sampai saat app background atau tertutup
  • delivery lintas Android lebih andal
  • jalur notifikasi lebih sederhana untuk diaudit dan dioperasikan

3.2 Targeting Notifikasi

Backend harus mampu memilih target notifikasi dengan mode berikut:

  • per email
  • per (tenant_kode, lokasi_id) untuk user aktif yang memiliki [tenant].pengguna_akses_lokasi.is_default = true
  • per (tenant_kode, tipe_pengguna) seperti admin, direksi, gudang, sales, customer, dan seterusnya
  • per aturan tenant (notifikasi_aturan + notifikasi_aturan_penerima) berdasarkan kode_event dan opsional jenis_kode

Aturan default tenant dibuat di V908__notifikasi_aturan_tenant.sql. Trigger mengirim target_type=aturan; backend membaca aturan tenant untuk menentukan penerima FCM. Dengan pola ini target penerima tidak hardcode di trigger dan bisa diubah per tenant/per jenis transaksi.

3.3 Registrasi Token Device

Alur registrasi token yang benar:

  1. user login ke Firebase
  2. backend menerbitkan JWT aplikasi
  3. setelah user memilih tenant, app mengambil token FCM
  4. app mengirim token itu ke backend
  5. backend menyimpan ke pusat.perangkat

FCM bukan sumber data bisnis. FCM hanya jalur pengantaran notifikasi.

3.4 Alur User Login Tapi Belum Punya Tenant

Alur operasional yang sekarang dipakai:

  1. user login berhasil ke Firebase dan terdaftar di ERP
  2. GET /api/tenants mengembalikan daftar kosong
  3. klien diarahkan ke mode umum/none
  4. di mode itu user dapat aksi Request Permission
  5. klien memanggil POST /api/notifikasi/request-akses-tenant
  6. backend mengirim FCM ke semua user aktif berperan admin dan direksi
  7. payload notifikasi membawa type=request_permission_tenant dan modul_kode=PENGATURAN_PENGGUNA
  8. saat notifikasi disentuh, aplikasi membuka screen Pengaturan Pengguna

3.5 Event Bisnis via pg_notify dan Aturan Tenant

Trigger di DB menghasilkan pg_notify untuk event bisnis utama. Payload yang dikirim backend ke Flutter selalu mengandung field data.modul_kode sehingga deep-link langsung membuka tab yang tepat, dan data membawa identifier baris terkait (barang_id, transaksi_id, dll.).

data.type Trigger pada Kondisi Default penerima data.modul_kode
draft_diajukan transaksi_draft UPDATE status status menjadi diajukan admin; SO/PM/WO punya override TRANSAKSI_DRAFT / dashboard modul
draft_disetujui transaksi_draft UPDATE status status menjadi disetujui pembuat draft; SO juga gudang TRANSAKSI_DRAFT
draft_ditolak / draft_revisi / draft_batal transaksi_draft UPDATE status status terkait pembuat draft TRANSAKSI_DRAFT
pj_dalam_proses / pj_cetak_nota / pj_ready / pj_selesai transaksi INSERT/UPDATE status PJ status 1..4 gudang/admin/sales sesuai tahap PENJUALAN_DASHBOARD
transaksi_final_dibuat transaksi INSERT PB/PL/BB/HP final gudang/admin sesuai jenis modul jenis transaksi
piutang_jatuh_tempo transaksi INSERT PJ/PI, jatuh tempo โ‰ค 7 hari direksi, admin, sales transaksi LAPORAN_PIUTANG
stok_kritis stok UPDATE qty qty โ†’ โ‰ค0 dari positif gudang, admin LAPORAN_STOK
jurnal_tidak_imbang transaksi INSERT (DEFERRED) jurnal debit โ‰  kredit admin, direksi LAPORAN_JURNAL

Tabel konfigurasi tenant:

  • notifikasi_aturan: event, template judul/body, modul_kode, aktif/nonaktif.
  • notifikasi_aturan_penerima: target peran, pengguna, kontak_field, supervisor_kontak_field, atau lokasi_field_default.
  • notifikasi_log: audit/inbox hasil resolve dan pengiriman.

Deep-link di klien Flutter:

  • NotificationNavigationService.storeFilter(modulKode, data) menyimpan payload bisnis sebelum tab dibuka.
  • Screen tujuan membaca filter via takeFilter(modulKode) di initState().
  • Banner notifikasi muncul di laporan yang dibuka dari notifikasi, menampilkan konteks (nomor_transaksi, jatuh_tempo, barang_id) agar user langsung paham.

4. Chat & AI (fitur baru) โ€” bangun API-first

Fitur baru (chat, asisten AI) tidak boleh mewarisi utang akses-DB-langsung. Dibangun API-first sejak awal:

Fitur Rekomendasi Catatan
Chat Pesan via backend API + WebSocket; simpan di Postgres tenant (kerja/sistem) atau Firestore bila perlu cepat & realtime out-of-the-box Jangan taruh data keuangan di sini. Chat = data komunikasi
Notifikasi FCM (push) + tabel notifikasi per tenant untuk riwayat/inbox Riwayat tetap di Postgres agar bisa diaudit
Asisten AI Backend memanggil model AI; akses data hanya lewat API/fungsi DB bernama, jangan beri AI kredensial DB langsung Batasi per tenant & peran (JWT)

Pegangan: fitur baru = percontohan cara benar. Kalau yang baru saja sudah "langsung tembak DB", peremajaan ini kalah sebelum mulai.


5. Klien Desktop: Delphi/DevExpress โ€” Strategi "Sunset", Bukan "Modernkan"

Kenyataan & perasaan pemilik (dihormati)

  • Suka Delphi karena DevExpress TdxDBGrid: kolom mudah di-custom, ada lookup. Itu produktivitas nyata, bukan sekadar selera.
  • Tidak suka: SDM paham Pascal makin langka.
  • Sadar bahaya akses DB langsung, tapi merasa "API + Delphi" sangat rumit, jadi merasa tak punya pilihan.

Pembingkaian ulang yang melegakan

Pemilik tidak perlu menyelesaikan "API + Delphi" (memang rumit & sia-sia). Yang benar:

Delphi  โ†’  BIARKAN apa adanya (akses DB langsung) selama transisi
        โ†’  JANGAN investasi meng-API-kan Delphi
        โ†’  PENSIUNKAN, diganti klien web/Flutter baru yang API-first

Jadi kerumitan "API+Delphi" tidak pernah perlu dipecahkan โ€” Delphi disetop, bukan dimodernkan. Energi diarahkan ke klien baru yang bersih.

Pengganti grid DevExpress (agar fitur tak "turun kelas")

Kekhawatiran utama: kehilangan kekayaan grid (kolom custom, lookup, edit inline, grouping). Rekomendasi yang mempertahankan paradigma DevExpress:

Target Rekomendasi utama Kenapa Alternatif
Web DevExtreme DataGrid atau DevExpress Blazor Grid (vendor sama: DevExpress) Model mental sama persis: lookup column, column chooser, edit, master-detail, grouping. Kurva belajar paling landai dari TdxDBGrid AG Grid (paling kuat, paradigma beda)
Android/Flutter Syncfusion Flutter DataGrid Sangat kaya: edit, sort, kolom custom, lookup bisa dibangun PlutoGrid (gratis, ringan)

Memilih DevExtreme/Blazor = tetap di ekosistem DevExpress yang sudah dikuasai pemilik, hanya pindah platform. Ini jembatan paling halus dari Delphi โ†’ web.

Urutan praktis

  1. Bangun backend API + auth (Firebase) lebih dulu (fondasi semua klien).
  2. Bangun klien baru (web DevExtreme/Blazor; Android Flutter) fitur per fitur, mulai dari yang sering dipakai (transaksi inti + grid).
  3. Delphi tetap jalan paralel (direct DB) sampai klien baru menutupi fiturnya.
  4. Setelah fitur tertutup & teruji โ†’ pensiunkan Delphi modul demi modul.

"API + Delphi" tidak ada di peta jalan. Yang ada: "API + klien baru" โ†’ "Delphi pensiun". Dengan bantuan AI, ini realistis.


6. Ringkasan Keputusan/Arah

Topik Arah Dampak
Autentikasi Firebase Auth (authN) + pusat.pengguna (authZ) Firebase-first: login selalu berhasil; terdaftar flag yang membuka akses ERP
Push Android FCM, dipicu pg_notify dari trigger โ†’ backend relai Tabel pusat.perangkat (fcm_token); tak ada HTTP di trigger; tanpa WebSocket
Chat/AI API-first; data komunikasi terpisah dari data keuangan Postgres = pencatat; Firebase = komunikasi/identitas
Klien desktop Delphi sunset, bukan di-API-kan Tak ada proyek "API+Delphi"; energi ke klien baru
Grid pengganti Web: DevExtreme/Blazor โ€ข Flutter: Syncfusion Paradigma DevExpress dipertahankan; kurva belajar landai
OLAP / dashboard analitik CDC PostgreSQL โ†’ ClickHouse via Debezium+Kafka Pipeline live sejak 2026-05-21 โ€” lihat database/cdc/

Dokumen terkait yang diselaraskan: 00 (stack), 04 (skema pengguna + alur login), 08 (identitas, keamanan). Keputusan dicatat di memori proyek.