Files
2026-04-22 09:11:46 +07:00

159 lines
3.9 KiB
Go

package aplicare
import (
"api-service/internal/config"
"context"
"fmt"
"time"
)
type SyncResult struct {
RunAt string `json:"run_at"`
TotalRooms int `json:"total_rooms"`
Changed int `json:"changed"`
Posted int `json:"posted"`
Errors []string `json:"errors,omitempty"`
DryRun bool `json:"dry_run"`
}
type Syncer struct {
simrs *SimrsDB
bpjs *BpjsClient
statePath string
dryRun bool
}
func NewSyncer(simrs *SimrsDB, cfg *config.Config, statePath string, dryRun bool) *Syncer {
return &Syncer{
simrs: simrs,
bpjs: NewBpjsClient(cfg.Bpjs),
statePath: statePath,
dryRun: dryRun,
}
}
func (s *Syncer) Sync(ctx context.Context) (*SyncResult, error) {
result := &SyncResult{
RunAt: time.Now().Format(time.RFC3339),
DryRun: s.dryRun,
}
// 1. Baca dari SIMRS
ruangans, err := s.simrs.GetRuangan(ctx)
if err != nil {
return nil, fmt.Errorf("baca m_ruang gagal: %w", err)
}
detailMap, err := s.simrs.GetBedDetails(ctx)
if err != nil {
return nil, fmt.Errorf("baca m_detail gagal: %w", err)
}
// 2. Transform
beds := buildBedData(ruangans, detailMap)
result.TotalRooms = len(beds)
// 3. Diff vs state lama
oldState, err := LoadState(s.statePath)
if err != nil || oldState == nil {
oldState = &State{Rooms: make(map[string]RoomState)}
}
newState := ComputeDiff(oldState, beds)
changedBeds := GetChangedBeds(newState, beds)
result.Changed = len(changedBeds)
// 4. Dry run — tampilkan perubahan di terminal
if s.dryRun {
if len(changedBeds) == 0 {
fmt.Println("[DRY RUN] Tidak ada perubahan")
} else {
fmt.Printf("[DRY RUN] %d ruangan berubah:\n", len(changedBeds))
for _, bed := range changedBeds {
old := newState.Rooms[bed.KodeRuang].OldValue
fmt.Printf(" → %-30s | kelas: %-6s | kapasitas: %d | tersedia: %d → %d\n",
bed.NamaRuang,
bed.KodeKelas,
bed.Kapasitas,
old.Tersedia,
bed.Tersedia,
)
result.Posted++
}
}
if err := SaveState(s.statePath, newState); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("simpan state gagal: %v", err))
}
WriteBatchLog(result)
return result, nil
}
// 5. POST ke BPJS hanya yang berubah
if len(changedBeds) > 0 {
fmt.Printf("[SYNC] %d ruangan akan dikirim ke BPJS:\n", len(changedBeds))
}
for _, bed := range changedBeds {
if bed.Kapasitas == 0 {
continue
}
oldTersedia := newState.Rooms[bed.KodeRuang].OldValue.Tersedia
start := time.Now()
err := s.bpjs.PostKamar(ctx, bed)
elapsed := time.Since(start).Milliseconds()
if err != nil {
msg := fmt.Sprintf("POST %s gagal: %v", bed.KodeRuang, err)
result.Errors = append(result.Errors, msg)
fmt.Printf(" → %-30s | kelas: %-6s | tersedia: %d → %d | [GAGAL] %v\n",
bed.NamaRuang, bed.KodeKelas, oldTersedia, bed.Tersedia, err)
WriteLog(SyncLog{
KodeRuang: bed.KodeRuang,
NamaRuang: bed.NamaRuang,
KodeKelas: bed.KodeKelas,
Kapasitas: bed.Kapasitas,
Tersedia: bed.Tersedia,
Action: "post",
Status: "gagal",
Error: err.Error(),
ResponseMs: elapsed,
})
continue
}
fmt.Printf(" → %-30s | kelas: %-6s | tersedia: %d → %d | [SUKSES] %dms\n",
bed.NamaRuang, bed.KodeKelas, oldTersedia, bed.Tersedia, elapsed)
WriteLog(SyncLog{
KodeRuang: bed.KodeRuang,
NamaRuang: bed.NamaRuang,
KodeKelas: bed.KodeKelas,
Kapasitas: bed.Kapasitas,
Tersedia: bed.Tersedia,
Action: "post",
Status: "sukses",
ResponseMs: elapsed,
})
result.Posted++
if room, ok := newState.Rooms[bed.KodeRuang]; ok {
room.OldValue = room.NewValue
room.Changed = false
room.LastSynced = time.Now().Format(time.RFC3339)
newState.Rooms[bed.KodeRuang] = room
}
}
// 6. Simpan state
if err := SaveState(s.statePath, newState); err != nil {
result.Errors = append(result.Errors, fmt.Sprintf("simpan state gagal: %v", err))
}
// 7. Tulis ringkasan ke log
WriteBatchLog(result)
return result, nil
}