Lewati ke konten utama

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.

#EntitasTabel SIMRSTabel CRMKunci pencocokanWatermarkLewat antrean?
1polyclinicb_ms_unitpolyclinicscode❌ langsung
2doctorb_ms_pegawaidoctorssimrs_doctor_id❌ langsung
3patientb_ms_pasienpatientssimrs_patient_idtgl_act✅ queued
4appointmentb_kunjungan + b_pelayanan + b_ms_pasienappointmentssimrs_visit_idtgl_act✅ queued
5billingb_kunjungan + b_tindakan + b_ms_pasienbillingssimrs_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_unitpolyclinics)

Kolom CRMSumber
namenama
codeid (bukan kode)
is_activeaktif

Filter: aktif = 1.

Kenapa code diisi dari id, bukan kode

b_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_pegawaidoctors)

Kolom CRMSumber
simrs_doctor_idid
full_namenama
specializationspesialisasi
is_activeaktif

Filter: spesialisasi_id > 0 ATAU kode_dokter_bpjs tidak null — yaitu, hanya pegawai yang benar-benar dokter.

3. Patient (b_ms_pasienpatients)

Kolom CRMSumberTransformasi
simrs_patient_idno_rm
full_namenama
nikno_ktpdipotong 16 karakter
gendersexLM, PF, lainnya → null
date_of_birthtgl_lahirzero-date dibersihkan
phonetelpdipotong 20 karakter
addressalamat
is_from_simrsselalu true
Zero-date MySQL vs PostgreSQL

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/PM/F juga wajib: kolom gender di PostgreSQL punya CHECK constraint IN ('M','F').

4. Appointment (b_kunjunganappointments)

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 CRMSumber
simrs_visit_idb_kunjungan.id
patient_iddicari dari patients.simrs_patient_id = no_rm
doctor_iddicari dari doctors.simrs_doctor_id = dokter_id
poly_iddicari dari polyclinics.code = unit_id
appointment_datetgl
statuspulang = 1Completed, selain itu Scheduled
Baris dilewati kalau relasinya belum ada

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_tindakanbillings)

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 CRMSumber
simrs_invoice_idno_billing
patient_iddicari dari no_rm
appointment_iddicari dari appointments.simrs_visit_id
total_amountSUM(biaya)
paid_amountSUM(bayar)
statuspaid >= total AND total > 0Paid, 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:

KolomIsi
batch_idBatch yang menulisnya
target_tableTabel CRM
row_pkPrimary key baris
opinsert atau update
before_jsonIsi baris sebelum (null untuk insert)
after_jsonData yang ditulis

Rollback

Tombol Rollback di halaman SIMRS Sync (hanya muncul untuk batch berstatus success).

Urutan terbalik itu penting

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.

Kenapa jadwal SIMRS bergerbang config, sapuan booking tidak

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.

KolomIsi
Entitaspolyclinic / doctor / patient / appointment / billing
Sumbermanual atau schedule
Status🟢 success · 🔵 running · 🟠 rolled_back · 🔴 failed
BarisJumlah baris ter-upsert
MulaiWaktu mulai
WatermarkNilai 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

EnvFungsiDefault
SIMRS_DB_HOST / PORT / DATABASE / USERNAME / PASSWORDKoneksi MySQL SIMRS
SIMRS_SYNC_CHUNKBaris per chunk1000
SIMRS_SYNC_SCHEDULE_ENABLEDNyalakan jadwal hariantrue
SIMRS_SYNC_SCHEDULE_TIMEJam jadwal (informatif)01:00

Di produksi, koneksi SIMRS melewati kontainer terowongan crm_simrs_tunnel.


Semua titik gagal & penanganannya

Titik gagalPerilakuPemulihan
Job timeout / proses dibunuhBatch tertinggal runningRun berikutnya menandainya failed dan melanjutkan dari cursor
Query SIMRS errorBatch → failed + pesan error, cursor dibiarkanJalankan ulang — melanjutkan dari titik terakhir
Zero-date MySQLDiubah jadi null sebelum ditulisOtomatis
gender di luar L/PDiisi nullOtomatis
Relasi belum ada (pasien/dokter/poli)Baris dilewati, tidak dihitungJalankan entitas prasyaratnya lebih dulu, lalu ulangi
Data salah masukRollback batch tersebut
Entitas tidak dikenal di commandDilogCek config/simrs.php