first commit
This commit is contained in:
@@ -0,0 +1,42 @@
|
||||
package encounter
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// PendaftaranDB merepresentasikan struktur gabungan dari tabel transaksi SIMRS dan master SATUDATA
|
||||
type PendaftaranDB struct {
|
||||
// --- Sumber: Database SIMRS (PostgreSQL) ---
|
||||
IdxDaftar int64 `db:"idxdaftar"`
|
||||
NoMR sql.NullString `db:"nomr"`
|
||||
TglReg sql.NullTime `db:"tglreg"`
|
||||
KdDokter sql.NullInt64 `db:"kddokter"`
|
||||
KdPoly sql.NullInt64 `db:"kdpoly"`
|
||||
KdCaraBayar sql.NullInt64 `db:"kdcarabayar"`
|
||||
Status sql.NullInt64 `db:"status"`
|
||||
Batal sql.NullString `db:"batal"`
|
||||
JamReg sql.NullTime `db:"jamreg"`
|
||||
MasukPoly sql.NullTime `db:"masukpoly"`
|
||||
KeluarPoly sql.NullTime `db:"keluarpoly"`
|
||||
|
||||
// --- Sumber: Database SIMRS (public.m_pasien) ---
|
||||
PatientName sql.NullString `db:"nama"`
|
||||
NIK sql.NullString `db:"nik"`
|
||||
|
||||
// --- Sumber: Database SIMRS Master Data (PostgreSQL) ---
|
||||
PatientIHS sql.NullString `db:"patient_ihs"`
|
||||
PractitionerIHS sql.NullString `db:"practitioner_ihs"`
|
||||
PractitionerNIK sql.NullString `db:"practitioner_nik"`
|
||||
LocationIHS sql.NullString `db:"location_ihs"`
|
||||
}
|
||||
|
||||
// EncounterSyncLog merepresentasikan struktur log sinkronisasi API SatuSehat.
|
||||
// Struktur ini mempermudah pembacaan payload (Request/Response) serta mendukung proses pengiriman ulang (rollback/resend).
|
||||
type EncounterSyncLog struct {
|
||||
IdxDaftar int64 `db:"idxdaftar"`
|
||||
EncounterID string `db:"encounter_id"` // ID dari SatuSehat jika status SUCCESS
|
||||
RequestPayload string `db:"request_payload"` // Teks JSON raw request
|
||||
ResponsePayload string `db:"response_payload"` // Teks JSON raw response
|
||||
Status string `db:"status"` // Status: SUCCESS / FAILED
|
||||
ErrorMessage string `db:"error_message"` // Pesan kegagalan (jika FAILED)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package encounter
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
// MapToInternalAPI memetakan data database menjadi payload untuk Internal API GoPrint
|
||||
func MapToInternalAPI(dbData *PendaftaranDB, orgID string) map[string]interface{} {
|
||||
// 1. Penentuan status encounter
|
||||
status := "arrived"
|
||||
if dbData.Batal.Valid && dbData.Batal.String == "Y" {
|
||||
status = "cancelled"
|
||||
} else if dbData.KeluarPoly.Valid {
|
||||
status = "finished"
|
||||
} else if dbData.MasukPoly.Valid {
|
||||
status = "in-progress"
|
||||
}
|
||||
|
||||
// 2. Format Period Waktu
|
||||
periodStart := time.Now()
|
||||
if dbData.MasukPoly.Valid {
|
||||
periodStart = dbData.MasukPoly.Time
|
||||
} else if dbData.JamReg.Valid {
|
||||
periodStart = dbData.JamReg.Time
|
||||
}
|
||||
|
||||
// 3. Ekstrak Kelengkapan ID IHS
|
||||
patientIHS := dbData.NoMR.String // Fallback ke NoMR jika IHS tidak ada
|
||||
if dbData.PatientIHS.Valid && dbData.PatientIHS.String != "" {
|
||||
patientIHS = dbData.PatientIHS.String
|
||||
}
|
||||
practitionerIHS := ""
|
||||
if dbData.PractitionerIHS.Valid && dbData.PractitionerIHS.String != "" {
|
||||
practitionerIHS = dbData.PractitionerIHS.String
|
||||
}
|
||||
locationIHS := ""
|
||||
if dbData.LocationIHS.Valid && dbData.LocationIHS.String != "" {
|
||||
locationIHS = dbData.LocationIHS.String
|
||||
}
|
||||
|
||||
// 4. Bangun Payload Internal API
|
||||
payload := map[string]interface{}{
|
||||
"encounter_id": fmt.Sprintf("%d", dbData.IdxDaftar),
|
||||
"organization_id": orgID,
|
||||
"patient_id": patientIHS,
|
||||
"patient_name": "Pasien " + patientIHS, // Nama fallback untuk lolos validasi usecase
|
||||
"practitioner_id": practitionerIHS,
|
||||
"practitioner_name": "Tenaga Kesehatan", // Nama fallback
|
||||
"location_id": locationIHS,
|
||||
"location_name": "Poli Faskes", // Nama fallback
|
||||
"status": status,
|
||||
"class": "AMB",
|
||||
"period_start": periodStart.Format(time.RFC3339),
|
||||
}
|
||||
|
||||
if dbData.KeluarPoly.Valid {
|
||||
payload["period_end"] = dbData.KeluarPoly.Time.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
return payload
|
||||
}
|
||||
|
||||
// MapToSyncLog membuat struktur data log histori untuk disimpan ke tabel database
|
||||
func MapToSyncLog(idxdaftar int64, encounterID string, reqPayload interface{}, respBody []byte, status string, errMsg string) EncounterSyncLog {
|
||||
reqBytes, _ := json.MarshalIndent(reqPayload, "", " ") // Indent agar JSON mudah dibaca saat dibuka di DB/Adminer
|
||||
return EncounterSyncLog{
|
||||
IdxDaftar: idxdaftar,
|
||||
EncounterID: encounterID,
|
||||
RequestPayload: string(reqBytes),
|
||||
ResponsePayload: string(respBody),
|
||||
Status: status,
|
||||
ErrorMessage: errMsg,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
package encounter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"service/internal/infrastructure/database"
|
||||
"service/pkg/utils/query"
|
||||
)
|
||||
|
||||
// Repository mendefinisikan kontrak akses database untuk worker encounter
|
||||
type Repository interface {
|
||||
GetNextPendaftaran(ctx context.Context, lastID int64) (*PendaftaranDB, error)
|
||||
SaveSatuSehatID(ctx context.Context, idxdaftar int64, encounterID string) error
|
||||
SaveSyncLog(ctx context.Context, logData EncounterSyncLog) error
|
||||
}
|
||||
|
||||
type repository struct {
|
||||
dbManager database.Service
|
||||
}
|
||||
|
||||
func NewRepository(dbManager database.Service) Repository {
|
||||
return &repository{dbManager: dbManager}
|
||||
}
|
||||
|
||||
func (r *repository) GetNextPendaftaran(ctx context.Context, lastID int64) (*PendaftaranDB, error) {
|
||||
var p PendaftaranDB
|
||||
|
||||
// 1. Ambil koneksi database SQLX SIMRS (PostgreSQL)
|
||||
simrsDB, err := r.dbManager.GetSQLXDB("simrs")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Inisiasi Query Builder (Disable security check untuk penggunaan internal & static)
|
||||
qb := query.NewSQLQueryBuilder(query.DBTypePostgreSQL).SetSecurityOptions(false, 0)
|
||||
|
||||
// 2. Ambil data Pendaftaran Utama dari SIMRS menggunakan Query Builder
|
||||
qSimrs := query.DynamicQuery{
|
||||
From: "public.t_pendaftaran",
|
||||
Aliases: "p", // Alias untuk tabel pendaftaran
|
||||
Fields: []query.SelectField{
|
||||
{Expression: "p.idxdaftar"},
|
||||
{Expression: "p.nomr"},
|
||||
{Expression: "p.tglreg"},
|
||||
{Expression: "p.kddokter"},
|
||||
{Expression: "p.kdpoly"},
|
||||
{Expression: "p.kdcarabayar"},
|
||||
{Expression: "p.status"},
|
||||
{Expression: "p.batal"},
|
||||
{Expression: "p.jamreg"},
|
||||
{Expression: "p.masukpoly"},
|
||||
{Expression: "p.keluarpoly"},
|
||||
{Expression: "m.nama"},
|
||||
{Expression: "COALESCE(NULLIF(m.noktp_baru, ''), m.noktp) AS nik"},
|
||||
},
|
||||
Joins: []query.Join{
|
||||
{
|
||||
Type: "LEFT",
|
||||
Table: "public.m_pasien",
|
||||
Alias: "m", // Alias untuk tabel master pasien
|
||||
OnConditions: query.FilterGroup{
|
||||
Filters: []query.DynamicFilter{
|
||||
// Relasi antar kolom: p.nomr = m.nomr
|
||||
query.CreateEqualFilter("p.nomr", "m.nomr"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Filters: []query.FilterGroup{
|
||||
{
|
||||
Filters: []query.DynamicFilter{
|
||||
query.CreateFilter("p.idxdaftar", query.OpGreaterThan, lastID),
|
||||
},
|
||||
},
|
||||
},
|
||||
Sort: []query.SortField{
|
||||
query.CreateAscSort("p.idxdaftar"),
|
||||
},
|
||||
Limit: 1,
|
||||
}
|
||||
|
||||
err = qb.ExecuteQueryRow(ctx, simrsDB, qSimrs, &p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 3. Lengkapi referensi IHS Number dari tabel master SATUDATA menggunakan Query Builder
|
||||
satuDataDB, err := r.dbManager.GetSQLXDB("satudata")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("gagal koneksi ke database satudata: %w", err)
|
||||
}
|
||||
|
||||
if p.NoMR.Valid && p.NoMR.String != "" {
|
||||
qPasien := query.DynamicQuery{
|
||||
From: "public.data_pasien",
|
||||
Fields: []query.SelectField{{Expression: "\"Nomor_satusehat\""}},
|
||||
Filters: []query.FilterGroup{{Filters: []query.DynamicFilter{query.CreateEqualFilter("\"Nomor_rekamedik\"", p.NoMR.String)}}},
|
||||
Limit: 1,
|
||||
}
|
||||
_ = qb.ExecuteQueryRow(ctx, satuDataDB, qPasien, &p.PatientIHS)
|
||||
}
|
||||
|
||||
if p.KdDokter.Valid {
|
||||
qDokter := query.DynamicQuery{
|
||||
From: "public.data_pegawai",
|
||||
Fields: []query.SelectField{
|
||||
{Expression: "\"Kode_satusehat\"", Alias: "practitioner_ihs"},
|
||||
{Expression: "nik", Alias: "practitioner_nik"}, // Sesuaikan "nik" jika nama kolom di DB Anda menggunakan huruf besar "\"NIK\""
|
||||
},
|
||||
Filters: []query.FilterGroup{{Filters: []query.DynamicFilter{query.CreateEqualFilter("\"Kode_DPJP\"", fmt.Sprintf("%d", p.KdDokter.Int64))}}},
|
||||
Limit: 1,
|
||||
}
|
||||
_ = qb.ExecuteQueryRow(ctx, satuDataDB, qDokter, &p)
|
||||
}
|
||||
|
||||
if p.KdPoly.Valid {
|
||||
qPoly := query.DynamicQuery{
|
||||
From: "public.daftar_lokasi_ruang",
|
||||
Fields: []query.SelectField{{Expression: "\"Kode_satusehat\""}},
|
||||
Filters: []query.FilterGroup{{Filters: []query.DynamicFilter{query.CreateEqualFilter("\"ID_ruang_simgos\"", fmt.Sprintf("%d", p.KdPoly.Int64))}}},
|
||||
Limit: 1,
|
||||
}
|
||||
_ = qb.ExecuteQueryRow(ctx, satuDataDB, qPoly, &p.LocationIHS)
|
||||
}
|
||||
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func (r *repository) SaveSatuSehatID(ctx context.Context, idxdaftar int64, encounterID string) error {
|
||||
simrsDB, err := r.dbManager.GetSQLXDB("simrs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Menggunakan transaksi DB
|
||||
tx, err := simrsDB.BeginTxx(ctx, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback() // Otomatis rollback bila gagal (atau tidak di-commit)
|
||||
|
||||
qb := query.NewSQLQueryBuilder(query.DBTypePostgreSQL).SetSecurityOptions(false, 0)
|
||||
|
||||
updateData := query.UpdateData{
|
||||
Columns: []string{"status_bridging"},
|
||||
Values: []interface{}{encounterID},
|
||||
}
|
||||
|
||||
filters := []query.FilterGroup{
|
||||
query.CreateAndFilterGroup([]query.DynamicFilter{query.CreateEqualFilter("idxdaftar", idxdaftar)}),
|
||||
}
|
||||
|
||||
// Eksekusi Update via Query Builder (melempar tx, bukan db, agar terbawa ke transaksi)
|
||||
_, err = qb.ExecuteUpdate(ctx, tx, "public.t_pendaftaran", updateData, filters)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Terapkan data
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
// SaveSyncLog menyimpan histori pengiriman (Request & Response) ke tabel log
|
||||
func (r *repository) SaveSyncLog(ctx context.Context, logData EncounterSyncLog) error {
|
||||
simrsDB, err := r.dbManager.GetSQLXDB("simrs")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
qb := query.NewSQLQueryBuilder(query.DBTypePostgreSQL).SetSecurityOptions(false, 0)
|
||||
|
||||
insertData := query.InsertData{
|
||||
Columns: []string{"idxdaftar", "encounter_id", "request_payload", "response_payload", "status", "error_message"},
|
||||
Values: []interface{}{
|
||||
logData.IdxDaftar, logData.EncounterID, logData.RequestPayload,
|
||||
logData.ResponsePayload, logData.Status, logData.ErrorMessage,
|
||||
},
|
||||
}
|
||||
|
||||
// Menggunakan UPSERT (ON CONFLICT) agar jika kita mengirim ulang (resend), log otomatis diperbarui
|
||||
conflictCols := []string{"idxdaftar"}
|
||||
updateCols := []string{"encounter_id", "request_payload", "response_payload", "status", "error_message"}
|
||||
|
||||
_, err = qb.ExecuteUpsert(ctx, simrsDB, "public.log_satusehat_encounter", insertData, conflictCols, updateCols)
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package encounter
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"service/internal/infrastructure/database"
|
||||
extapi "service/internal/worker/interface"
|
||||
)
|
||||
|
||||
const trackerFile = "last_migrated_encounter_id.txt"
|
||||
|
||||
// Config menyimpan konfigurasi untuk worker Encounter
|
||||
type Config struct {
|
||||
DBManager database.Service
|
||||
InternalBaseURL string // URL Internal API (contoh: http://localhost:8080/api/v1)
|
||||
InternalToken string // Token jika endpoint internal dilindungi Auth
|
||||
OrganizationID string
|
||||
}
|
||||
|
||||
// WorkerService adalah interface untuk trigger scheduler
|
||||
type WorkerService interface {
|
||||
Run(ctx context.Context)
|
||||
}
|
||||
|
||||
type worker struct {
|
||||
cfg Config
|
||||
repo Repository
|
||||
apiClient extapi.Client
|
||||
}
|
||||
|
||||
// NewWorker inisiasi worker service baru
|
||||
func NewWorker(cfg Config) WorkerService {
|
||||
return &worker{
|
||||
cfg: cfg,
|
||||
repo: NewRepository(cfg.DBManager),
|
||||
apiClient: extapi.NewClient(15 * time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
// Run adalah fungsi utama worker/scheduler
|
||||
func (w *worker) Run(ctx context.Context) {
|
||||
log.Println("[ENCOUNTER WORKER] Memulai proses sinkronisasi ke API Satu Sehat...")
|
||||
|
||||
lastID := w.readLastID()
|
||||
log.Printf("[ENCOUNTER WORKER] Melanjutkan migrasi dari idxdaftar > %d\n", lastID)
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
log.Println("[ENCOUNTER WORKER] Proses dihentikan oleh sistem.")
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
pendaftaran, err := w.repo.GetNextPendaftaran(ctx, lastID)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
time.Sleep(5 * time.Second) // Tunggu data baru jika habis
|
||||
continue
|
||||
}
|
||||
log.Printf("[ENCOUNTER WORKER] Gagal mengambil data database: %v\n", err)
|
||||
time.Sleep(5 * time.Second)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Printf("[ENCOUNTER WORKER] Memproses Pendaftaran ID: %d, RM: %s\n", pendaftaran.IdxDaftar, pendaftaran.NoMR.String)
|
||||
|
||||
// Fallback pencarian IHS Number lewat NIK jika data master kosong
|
||||
if !pendaftaran.PatientIHS.Valid || pendaftaran.PatientIHS.String == "" {
|
||||
if pendaftaran.NIK.Valid && pendaftaran.NIK.String != "" {
|
||||
log.Printf("[ENCOUNTER WORKER] Patient IHS tidak ditemukan untuk ID %d. Mencoba mencari dari NIK: %s\n", pendaftaran.IdxDaftar, pendaftaran.NIK.String)
|
||||
|
||||
searchURL := fmt.Sprintf("%s/satusehat/reference/patient/nik/%s", strings.TrimRight(w.cfg.InternalBaseURL, "/"), pendaftaran.NIK.String)
|
||||
searchResp, searchCode, searchErr := w.apiClient.GetJSON(ctx, searchURL, w.cfg.InternalToken)
|
||||
|
||||
if searchErr == nil && searchCode == http.StatusOK {
|
||||
var searchResult map[string]interface{}
|
||||
if err := json.Unmarshal(searchResp, &searchResult); err == nil {
|
||||
// Ekstrak ID (IHS Number) dari atribut "data.id" pada format API standar Anda
|
||||
if data, ok := searchResult["data"].(map[string]interface{}); ok {
|
||||
if fhirID, ok := data["id"].(string); ok && fhirID != "" {
|
||||
pendaftaran.PatientIHS.String = fhirID
|
||||
pendaftaran.PatientIHS.Valid = true
|
||||
log.Printf("[ENCOUNTER WORKER] Berhasil mendapatkan IHS %s dari pencarian NIK.\n", fhirID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback pencarian Practitioner IHS Number lewat NIK jika data master kosong
|
||||
if !pendaftaran.PractitionerIHS.Valid || pendaftaran.PractitionerIHS.String == "" {
|
||||
if pendaftaran.PractitionerNIK.Valid && pendaftaran.PractitionerNIK.String != "" {
|
||||
log.Printf("[ENCOUNTER WORKER] Practitioner IHS tidak ditemukan untuk ID %d. Mencoba mencari dari NIK: %s\n", pendaftaran.IdxDaftar, pendaftaran.PractitionerNIK.String)
|
||||
|
||||
searchURL := fmt.Sprintf("%s/satusehat/reference/practitioner/nik/%s", strings.TrimRight(w.cfg.InternalBaseURL, "/"), pendaftaran.PractitionerNIK.String)
|
||||
searchResp, searchCode, searchErr := w.apiClient.GetJSON(ctx, searchURL, w.cfg.InternalToken)
|
||||
|
||||
if searchErr == nil && searchCode == http.StatusOK {
|
||||
var searchResult map[string]interface{}
|
||||
if err := json.Unmarshal(searchResp, &searchResult); err == nil {
|
||||
if data, ok := searchResult["data"].(map[string]interface{}); ok {
|
||||
if fhirID, ok := data["id"].(string); ok && fhirID != "" {
|
||||
pendaftaran.PractitionerIHS.String = fhirID
|
||||
pendaftaran.PractitionerIHS.Valid = true
|
||||
log.Printf("[ENCOUNTER WORKER] Berhasil mendapatkan Practitioner IHS %s dari pencarian NIK.\n", fhirID)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Skip record jika data IHS masih belum ada (gagal dari DB dan gagal dicari dari NIK)
|
||||
if !pendaftaran.PatientIHS.Valid || pendaftaran.PatientIHS.String == "" {
|
||||
log.Printf("[ENCOUNTER WORKER] Pendaftaran ID %d di-skip karena Patient IHS tidak ditemukan dan pencarian NIK gagal/kosong.\n", pendaftaran.IdxDaftar)
|
||||
w.updateTracker(&lastID, pendaftaran.IdxDaftar)
|
||||
continue
|
||||
}
|
||||
|
||||
internalPayload := MapToInternalAPI(pendaftaran, w.cfg.OrganizationID)
|
||||
|
||||
// Mengirim data ke endpoint internal API Faskes (POST /satusehat/encounter)
|
||||
reqURL := fmt.Sprintf("%s/satusehat/encounter", strings.TrimRight(w.cfg.InternalBaseURL, "/"))
|
||||
|
||||
// Eksekusi HTTP request menggunakan Interface external client yang sudah dibuat
|
||||
respBody, statusCode, err := w.apiClient.PostJSON(ctx, reqURL, w.cfg.InternalToken, internalPayload)
|
||||
|
||||
// Evaluasi Response dan Ekstrak ID (Jika Sukses)
|
||||
var status, errMsg, fhirID string
|
||||
if err != nil {
|
||||
status = "FAILED"
|
||||
errMsg = err.Error()
|
||||
log.Printf("[ENCOUNTER WORKER] Gagal HTTP Request ke Internal API (ID %d): %v\n", pendaftaran.IdxDaftar, err)
|
||||
} else if statusCode != http.StatusOK && statusCode != http.StatusCreated {
|
||||
status = "FAILED"
|
||||
errMsg = fmt.Sprintf("HTTP %d", statusCode)
|
||||
log.Printf("[ENCOUNTER WORKER] Gagal Response Internal API ID %d. HTTP: %d, Response: %s\n", pendaftaran.IdxDaftar, statusCode, string(respBody))
|
||||
} else {
|
||||
status = "SUCCESS"
|
||||
// Ambil ID FHIR Kemenkes dari response format Standar Golang Faskes (Result.Data)
|
||||
var result map[string]interface{}
|
||||
if errUnmarshal := json.Unmarshal(respBody, &result); errUnmarshal == nil {
|
||||
if data, ok := result["data"].(map[string]interface{}); ok {
|
||||
if id, ok := data["id"].(string); ok {
|
||||
fhirID = id
|
||||
}
|
||||
} else if id, ok := result["id"].(string); ok {
|
||||
fhirID = id
|
||||
}
|
||||
}
|
||||
log.Printf("[ENCOUNTER WORKER] Sukses mengirim ID %d. Response: %s\n", pendaftaran.IdxDaftar, truncate(string(respBody), 150))
|
||||
}
|
||||
|
||||
// Simpan histori/log ke database untuk kemudahan audit dan resend/rollback
|
||||
syncLog := MapToSyncLog(pendaftaran.IdxDaftar, fhirID, internalPayload, respBody, status, errMsg)
|
||||
_ = w.repo.SaveSyncLog(ctx, syncLog)
|
||||
|
||||
// Jika sukses, simpan ID FHIR ke tabel transaksi utama
|
||||
if status == "SUCCESS" && fhirID != "" {
|
||||
_ = w.repo.SaveSatuSehatID(ctx, pendaftaran.IdxDaftar, fhirID)
|
||||
}
|
||||
|
||||
// Update file tracker (PENTING: Lanjut ke ID berikutnya agar tidak stuck pada data yang selalu error)
|
||||
w.updateTracker(&lastID, pendaftaran.IdxDaftar)
|
||||
|
||||
// Jika gagal, tahan sebentar sebelum lanjut (Rate limiting/jeda darurat)
|
||||
if status == "FAILED" {
|
||||
time.Sleep(5 * time.Second)
|
||||
} else {
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (w *worker) updateTracker(lastID *int64, currentID int64) {
|
||||
*lastID = currentID
|
||||
w.saveLastID(*lastID)
|
||||
}
|
||||
|
||||
// --- Tracker File Management ---
|
||||
|
||||
func (w *worker) readLastID() int64 {
|
||||
data, err := os.ReadFile(trackerFile)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
idStr := strings.TrimSpace(string(data))
|
||||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func (w *worker) saveLastID(id int64) {
|
||||
os.WriteFile(trackerFile, []byte(strconv.FormatInt(id, 10)), 0644)
|
||||
}
|
||||
|
||||
func truncate(s string, max int) string {
|
||||
if len(s) > max {
|
||||
return s[:max] + "..."
|
||||
}
|
||||
return s
|
||||
}
|
||||
Reference in New Issue
Block a user