Alur SIMRS Sync
Menarik data klinis dari gudang data SIMRS rumah sakit (dev_simrs_b, MySQL) ke database CRM (PostgreSQL). Satu arah saja — CRM tidak pernah menulis balik ke SIMRS.
Gambaran besar
Lima entitas dan urutannya
Urutan ini wajib — ia aman terhadap foreign key. Janji temu butuh pasien/dokter/poli sudah ada; tagihan butuh janji temu.
| # | Entitas | Tabel SIMRS | Tabel CRM | Kunci pencocokan | Watermark | Lewat antrean? |
|---|---|---|---|---|---|---|
| 1 | polyclinic | b_ms_unit | polyclinics | code | — | ❌ langsung |
| 2 | doctor | b_ms_pegawai | doctors | simrs_doctor_id | — | ❌ langsung |
| 3 | patient | b_ms_pasien | patients | simrs_patient_id | tgl_act | ✅ queued |
| 4 | appointment | b_kunjungan + b_pelayanan + b_ms_pasien | appointments | simrs_visit_id | tgl_act | ✅ queued |
| 5 | billing | b_kunjungan + b_tindakan + b_ms_pasien | billings | simrs_invoice_id | — | ✅ queued |
Entitas bertanda queued dijalankan lewat antrean pekerjaan karena tabelnya besar (timeout job dinaikkan sampai 6 jam untuk backfill besar).
Detail pemetaan per entitas
1. Polyclinic (b_ms_unit → polyclinics)
| Kolom CRM | Sumber |
|---|---|
name | nama |
code | id (bukan kode) |
is_active | aktif |
Filter: aktif = 1.
code diisi dari id, bukan kodeb_ms_unit.kode tidak unik di SIMRS — banyak unit memakai kode yang sama. Karena itu poliklinik dikunci pada id unit yang unik, yang juga merupakan nilai yang dirujuk b_kunjungan.unit_id.
2. Doctor (b_ms_pegawai → doctors)
| Kolom CRM | Sumber |
|---|---|
simrs_doctor_id | id |
full_name | nama |
specialization | spesialisasi |
is_active | aktif |
Filter: spesialisasi_id > 0 ATAU kode_dokter_bpjs tidak null — yaitu, hanya pegawai yang benar-benar dokter.
3. Patient (b_ms_pasien → patients)
| Kolom CRM | Sumber | Transformasi |
|---|---|---|
simrs_patient_id | no_rm | — |
full_name | nama | — |
nik | no_ktp | dipotong 16 karakter |
gender | sex | L → M, P → F, lainnya → null |
date_of_birth | tgl_lahir | zero-date dibersihkan |
phone | telp | dipotong 20 karakter |
address | alamat | — |
is_from_simrs | — | selalu true |
SIMRS menyimpan tanggal gaya MySQL yang ditolak PostgreSQL: 0000-00-00, YYYY-00-00, YYYY-MM-00. Semua bentuk itu diubah jadi null sebelum ditulis. Tanpa pembersihan ini, seluruh sync pasien gagal di baris pertama yang tanggal lahirnya kosong.
Pemetaan L/P → M/F juga wajib: kolom gender di PostgreSQL punya CHECK constraint IN ('M','F').
4. Appointment (b_kunjungan → appointments)
Query menggabungkan tiga tabel dan mengambil dokter pertama dari layanan:
SELECT b_kunjungan.id, b_kunjungan.tgl, b_kunjungan.pulang,
b_kunjungan.unit_id, b_kunjungan.tgl_act,
b_ms_pasien.no_rm AS pasien_no_rm,
MIN(b_pelayanan.dokter_id) AS dokter_id
FROM b_kunjungan
LEFT JOIN b_ms_pasien ON b_ms_pasien.id = b_kunjungan.pasien_id
LEFT JOIN b_pelayanan ON b_pelayanan.kunjungan_id = b_kunjungan.id
GROUP BY b_kunjungan.id, ..., b_ms_pasien.no_rm
| Kolom CRM | Sumber |
|---|---|
simrs_visit_id | b_kunjungan.id |
patient_id | dicari dari patients.simrs_patient_id = no_rm |
doctor_id | dicari dari doctors.simrs_doctor_id = dokter_id |
poly_id | dicari dari polyclinics.code = unit_id |
appointment_date | tgl |
status | pulang = 1 → Completed, selain itu Scheduled |
Kalau pasien, dokter, atau poliklinik tidak ditemukan di CRM, mapRow() mengembalikan array kosong dan baris itu dilewati (tidak dihitung sebagai upsert). Ini yang membuat urutan FK-safe penting: jalankan pasien/dokter/poli lebih dulu.
5. Billing (b_kunjungan + b_tindakan → billings)
SELECT b_kunjungan.id, b_kunjungan.no_billing,
b_ms_pasien.no_rm AS pasien_no_rm,
SUM(b_tindakan.biaya) AS total_amount,
SUM(b_tindakan.bayar) AS paid_amount
FROM b_kunjungan
JOIN b_tindakan ON b_tindakan.kunjungan_id = b_kunjungan.id
LEFT JOIN b_ms_pasien ON b_ms_pasien.id = b_kunjungan.pasien_id
GROUP BY b_kunjungan.id, b_kunjungan.no_billing, b_ms_pasien.no_rm
| Kolom CRM | Sumber |
|---|---|
simrs_invoice_id | no_billing |
patient_id | dicari dari no_rm |
appointment_id | dicari dari appointments.simrs_visit_id |
total_amount | SUM(biaya) |
paid_amount | SUM(bayar) |
status | paid >= total AND total > 0 → Paid, selain itu Unpaid |
Dilewati bila pasien tidak ditemukan atau no_billing kosong.
Mesin sinkronisasi: SyncManager
Tiga konsep kunci
Watermark — sinkronisasi bertahap
Entitas yang punya kolom tgl_act (pasien, janji temu) hanya menarik baris yang berubah setelah watermark terakhir. Watermark dibekukan di awal lintasan dan hanya dimajukan kalau lintasan selesai sempurna.
Entitas tanpa watermark (poliklinik, dokter, tagihan) selalu full refresh — tabelnya kecil atau nilainya agregat yang harus dihitung ulang.
Cursor — melanjutkan setelah terputus
Ini yang membuat backfill besar tidak perlu diulang dari nol.
Snapshot — supaya bisa di-rollback
Setiap baris yang ditulis meninggalkan satu baris sync_row_snapshots:
| Kolom | Isi |
|---|---|
batch_id | Batch yang menulisnya |
target_table | Tabel CRM |
row_pk | Primary key baris |
op | insert atau update |
before_json | Isi baris sebelum (null untuk insert) |
after_json | Data yang ditulis |
Rollback
Tombol Rollback di halaman SIMRS Sync (hanya muncul untuk batch berstatus success).
Kalau satu baris disentuh dua kali dalam satu batch, mengembalikannya dari yang terbaru dulu memastikan nilai akhirnya adalah kondisi paling awal.
Watermark hanya dimundurkan kalau batch ini memang batch terakhir entitas tersebut — supaya rollback batch lama tidak merusak posisi sinkronisasi yang sudah maju.
Cara menjalankan
Tombol di UI selalu lewat antrean (RunSimrsSyncJob), sehingga butuh queue worker hidup. Perintah artisan berjalan langsung.
Jadwal otomatis: 0 1 * * * zona Asia/Jakarta, withoutOverlapping(), onOneServer(). Bisa dimatikan lewat SIMRS_SYNC_SCHEDULE_ENABLED=false.
simrs:sync menarik seluruh gudang data SIMRS eksternal — sesuatu yang staging dan mesin lokal memang tidak boleh gempur. Sapuan bookings:reconcile sebaliknya hanya menyentuh baris milik aplikasi sendiri, dan ia menjaga uang yang sudah diterima — jadi ia sengaja tanpa saklar.
Halaman SIMRS Sync (/admin/simrs-sync)
Menampilkan riwayat sync_batches, terbaru di atas.
| Kolom | Isi |
|---|---|
| Entitas | polyclinic / doctor / patient / appointment / billing |
| Sumber | manual atau schedule |
| Status | 🟢 success · 🔵 running · 🟠 rolled_back · 🔴 failed |
| Baris | Jumlah baris ter-upsert |
| Mulai | Waktu mulai |
| Watermark | Nilai watermark akhir |
Aksi per baris:
- View Query — menampilkan hingga 3 query SIMRS pertama yang benar-benar dijalankan, lengkap dengan nilai bindingnya. Berguna untuk memverifikasi data langsung di SIMRS.
- Rollback — hanya untuk batch
success.
Konfigurasi
| Env | Fungsi | Default |
|---|---|---|
SIMRS_DB_HOST / PORT / DATABASE / USERNAME / PASSWORD | Koneksi MySQL SIMRS | — |
SIMRS_SYNC_CHUNK | Baris per chunk | 1000 |
SIMRS_SYNC_SCHEDULE_ENABLED | Nyalakan jadwal harian | true |
SIMRS_SYNC_SCHEDULE_TIME | Jam jadwal (informatif) | 01:00 |
Di produksi, koneksi SIMRS melewati kontainer terowongan crm_simrs_tunnel.
Semua titik gagal & penanganannya
| Titik gagal | Perilaku | Pemulihan |
|---|---|---|
| Job timeout / proses dibunuh | Batch tertinggal running | Run berikutnya menandainya failed dan melanjutkan dari cursor |
| Query SIMRS error | Batch → failed + pesan error, cursor dibiarkan | Jalankan ulang — melanjutkan dari titik terakhir |
| Zero-date MySQL | Diubah jadi null sebelum ditulis | Otomatis |
gender di luar L/P | Diisi null | Otomatis |
| Relasi belum ada (pasien/dokter/poli) | Baris dilewati, tidak dihitung | Jalankan entitas prasyaratnya lebih dulu, lalu ulangi |
| Data salah masuk | — | Rollback batch tersebut |
| Entitas tidak dikenal di command | Dilog | Cek config/simrs.php |