Pengambilan Data KFA Pengiriman data medication dan service request

This commit is contained in:
meninjar
2026-05-04 03:24:01 +00:00
parent 24d25aa14a
commit e8a0642291
80 changed files with 1718354 additions and 263 deletions

No files matched your search

+49 -12
View File
@@ -18,7 +18,8 @@ import (
)
const trackerFile = "internal/worker/satusehat/medication/last_medication_id.txt"
const mappingFile = "internal/worker/satusehat/medication/medication_mapping.txt"
const mappingBaseDir = "internal/worker/satusehat/medication/mapperdata"
const rateLimitSleep = 60 * time.Second
type Config struct {
DBManager database.Service
@@ -98,7 +99,17 @@ func (w *worker) Run(ctx context.Context) {
internalPayload := MapToInternalAPI(med, w.cfg.OrganizationID, kfaDisplay, formCode, formDisplay)
fhirID, respBody, status, errMsg, shouldRetry := w.sendToInternalAPI(ctx, internalPayload, med.IdxPesanObat)
fhirID, respBody, status, errMsg, shouldRetry, rateLimited := w.sendToInternalAPI(ctx, internalPayload, med.IdxPesanObat)
if rateLimited {
logger.Default().Warn("[MEDICATION WORKER] ⏸️ Rate limit dari Satu Sehat - jeda dan akan retry dokumen yang sama",
logger.Int64("idxpesanobat", med.IdxPesanObat),
logger.String("response", truncate(string(respBody), 300)),
logger.String("sleep", rateLimitSleep.String()),
)
time.Sleep(rateLimitSleep)
continue
}
// Menyimpan payload request dan response ke dalam log
logger.Default().Info("[MEDICATION WORKER] Detail Transaksi",
@@ -132,7 +143,7 @@ func (w *worker) Run(ctx context.Context) {
}
}
func (w *worker) sendToInternalAPI(ctx context.Context, payload map[string]interface{}, idxPesanObat int64) (fhirID string, respBody []byte, status string, errMsg string, shouldRetry bool) {
func (w *worker) sendToInternalAPI(ctx context.Context, payload map[string]interface{}, idxPesanObat int64) (fhirID string, respBody []byte, status string, errMsg string, shouldRetry bool, rateLimited bool) {
reqURL := fmt.Sprintf("%s/satusehat/medication", strings.TrimRight(w.cfg.InternalBaseURL, "/"))
respBody, statusCode, err := w.apiClient.PostJSON(ctx, reqURL, w.tokenManager.GetAccessToken(), payload)
@@ -142,7 +153,7 @@ func (w *worker) sendToInternalAPI(ctx context.Context, payload map[string]inter
logger.Any("payload_sent", payload),
logger.ErrorField(err),
)
return "", nil, "FAILED", err.Error(), true
return "", nil, "FAILED", err.Error(), true, false
}
if statusCode == http.StatusUnauthorized {
@@ -151,7 +162,7 @@ func (w *worker) sendToInternalAPI(ctx context.Context, payload map[string]inter
newToken, refreshErr := w.tokenManager.ForceRefreshAndGetToken(ctx)
if refreshErr != nil {
logger.Default().Error("[MEDICATION WORKER] Gagal refresh token, akan mencoba lagi pada iterasi berikutnya.", logger.Int64("idxpesanobat", idxPesanObat), logger.ErrorField(refreshErr))
return "", respBody, "FAILED", refreshErr.Error(), true
return "", respBody, "FAILED", refreshErr.Error(), true, false
}
logger.Default().Info("[MEDICATION WORKER] Token berhasil di-refresh, mencoba ulang request...", logger.Int64("idxpesanobat", idxPesanObat))
@@ -161,10 +172,15 @@ func (w *worker) sendToInternalAPI(ctx context.Context, payload map[string]inter
logger.Int64("idxpesanobat", idxPesanObat),
logger.ErrorField(err),
)
return "", nil, "FAILED", err.Error(), true
return "", nil, "FAILED", err.Error(), true, false
}
}
if isRateLimitResponse(statusCode, respBody) {
errMsg = fmt.Sprintf("HTTP %d - rate limit Satu Sehat", statusCode)
return "", respBody, "RATE_LIMITED", errMsg, true, true
}
if statusCode != http.StatusOK && statusCode != http.StatusCreated {
errMsg = fmt.Sprintf("HTTP %d", statusCode)
logger.Default().Error("[MEDICATION WORKER] ❌ Respons Error dari API Part 3",
@@ -173,10 +189,10 @@ func (w *worker) sendToInternalAPI(ctx context.Context, payload map[string]inter
logger.String("response", truncate(string(respBody), 200)),
)
if statusCode >= 500 || statusCode == http.StatusTooManyRequests {
return "", respBody, "FAILED", errMsg, true
if statusCode >= 500 {
return "", respBody, "FAILED", errMsg, true, false
}
return "", respBody, "FAILED", errMsg, false
return "", respBody, "FAILED", errMsg, false, false
}
var result map[string]interface{}
@@ -194,7 +210,21 @@ func (w *worker) sendToInternalAPI(ctx context.Context, payload map[string]inter
logger.String("fhir_id", fhirID),
)
return fhirID, respBody, "SUCCESS", "", false
return fhirID, respBody, "SUCCESS", "", false, false
}
// isRateLimitResponse mendeteksi respons rate-limit dari Satu Sehat
// (HTTP 429 atau pesan body yang mengindikasikan limit pengiriman terlampaui).
func isRateLimitResponse(statusCode int, respBody []byte) bool {
if statusCode == http.StatusTooManyRequests {
return true
}
body := strings.ToLower(string(respBody))
return strings.Contains(body, "too many requests") ||
strings.Contains(body, "rate limit") ||
strings.Contains(body, "limit exceeded") ||
strings.Contains(body, "limit pengiriman") ||
strings.Contains(body, "quota exceeded")
}
func (w *worker) fetchKFADetails(ctx context.Context, kfaCode string) (kfaDisplay, formCode, formDisplay string) {
@@ -301,8 +331,15 @@ func (w *worker) ensureTrackerFileExists() {
}
func (w *worker) saveMapping(idxPesanObat int64, fhirID string) {
os.MkdirAll("internal/worker/satusehat/medication", 0755)
f, err := os.OpenFile(mappingFile, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
now := logger.LocalNow()
monthDir := filepath.Join(mappingBaseDir, now.Format("2006-01"))
if err := os.MkdirAll(monthDir, 0755); err != nil {
logger.Default().Error("[MEDICATION WORKER] Gagal membuat direktori mapping bulanan", logger.ErrorField(err))
return
}
mappingPath := filepath.Join(monthDir, now.Format("2006-01-02")+".txt")
f, err := os.OpenFile(mappingPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil {
logger.Default().Error("[MEDICATION WORKER] Gagal membuka file mapping", logger.ErrorField(err))
return