package master import ( "api-service/internal/config" "api-service/internal/database" "api-service/internal/models" "api-service/internal/models/master" "api-service/pkg/logger" "context" "database/sql" "fmt" "github.com/gin-gonic/gin" "github.com/go-playground/validator/v10" "github.com/jmoiron/sqlx" "net/http" "time" ) func init() { once.Do(func() { db = database.New(config.LoadConfig()) validate = validator.New() // Register custom validations if needed validate.RegisterValidation("retribusi_status", validateloketstatus) if db == nil { logger.Fatal("Failed to initialize database connection") } }) } func validateloketstatus(fl validator.FieldLevel) bool { return models.IsValidStatus(fl.Field().String()) } type LoketHandler struct { db database.Service } func NewLoketGHandler() *LoketHandler { return &LoketHandler{ db: db, } } func (h *LoketHandler) GetLoket(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), 120*time.Second) defer cancel() IdLoket := c.Param("loket") dbConn, err := h.db.GetSQLXDB("postgres_antrean") if err != nil { logger.Error("Database connection failed", map[string]interface{}{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": "Database connection failed"}) return } remainingQuota, err := h.GetRemainingQuota(ctx, dbConn, IdLoket) if err != nil { logger.Warn("Failed to get remaining quota, defaulting to 0", map[string]interface{}{ "error": err.Error(), }) remainingQuota = 0 } ticketQueue, err := h.GetTicketQueueData(ctx, dbConn, IdLoket) if err != nil { if err == sql.ErrNoRows { c.JSON(http.StatusOK, master.TicketQueueResponse{ Message: "Tidak ada antrian ticket", Data: []*master.LoketTiketResponse{}, Meta: map[string]interface{}{ "total": 0, "counter_id": IdLoket, }, }) return } logger.Error("Failed to get ticket queue", map[string]interface{}{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve ticket queue"}) return } mappedData := h.MapToLoketTiketResponse(ticketQueue) c.JSON(http.StatusOK, master.TicketQueueResponse{ Message: "Data antrian ticket berhasil diambil", KuotaLoket: remainingQuota, Data: mappedData, Meta: map[string]interface{}{ "total": len(ticketQueue), "counter_id": IdLoket, }, }) } func (h *LoketHandler) GetTicketQueueData(ctx context.Context, dbConn *sqlx.DB, counterID string) ([]*master.TicketQueue, error) { query := ` select tpvhs.id ,tpvhs.healtcare_service_code ,tpvhs.ticket ,tpvhs.datetime_start, tpv.visit_code , mhs."name" , mhs.code ,mhss.shift_number, rpt."name" as "payment", rvs."name" as "posisi", rvs.description as "deskripsi", tpvhsvs.datetime as "waktuposisi" from "transaction".tr_patient_visit_healthcare_service tpvhs left join master.ms_healthcare_service mhs on tpvhs.fk_ms_healthcare_service_id = mhs.id left join master.ms_registration_counter mrc on tpvhs.fk_ms_registration_counter_id = mrc.id left join "transaction".tr_patient_visit tpv on tpvhs.fk_tr_patient_visit_id = tpv.id left join reference.ref_healthcare_type rht on tpvhs.fk_ref_healthcare_type_id = rht.id left join reference.ref_service_type rst on tpvhs.fk_ref_service_type_id = rst.id left join master.ms_sub_healthcare_service mshs on tpvhs.fk_ms_sub_healthcare_service_id = mshs.id left join reference.ref_visit_type rvt on tpvhs.fk_ref_visit_type_id = rvt.id left join reference.ref_payment_type rpt on tpvhs.fk_ref_payment_type_id = rpt.id left join master.ms_healthcare_service_shift mhss on mhss.fk_ms_healthcare_service_id = mhs.id left join "transaction".tr_patient_visit_healthcare_service_visit_status tpvhsvs on tpvhsvs.fk_tr_patient_visit_healthcare_service_id =tpvhs.id left join reference.ref_visit_status rvs on tpvhsvs.fk_ref_visit_status_id = rvs.id where mrc.id = $1 and tpvhs.active = true and tpv.active = true ORDER BY tpvhs.datetime_start ASC ` rows, err := dbConn.QueryContext(ctx, query, counterID) if err != nil { return nil, fmt.Errorf("failed to execute query: %w", err) } defer rows.Close() var ticketQueue []*master.TicketQueue for rows.Next() { var ticket master.TicketQueue err := rows.Scan( &ticket.ID, &ticket.HealthcareServiceCode, &ticket.Ticket, &ticket.DatetimeStart, &ticket.VisitCode, &ticket.HealthcareServiceName, &ticket.HealthcareServiceCodeMS, &ticket.ShiftNumber, &ticket.PaymentTypeName, &ticket.Posisi, &ticket.Deskripsi, &ticket.WaktuPosisi, ) if err != nil { return nil, fmt.Errorf("failed to scan row: %w", err) } ticketQueue = append(ticketQueue, &ticket) } if err = rows.Err(); err != nil { return nil, fmt.Errorf("rows iteration error: %w", err) } // Check if no data found if len(ticketQueue) == 0 { return nil, sql.ErrNoRows } logger.Info("Ticket queue retrieved successfully", map[string]interface{}{ "counterID": counterID, "total": len(ticketQueue), }) return ticketQueue, nil } func (h *LoketHandler) MapToLoketTiketResponse(tickets []*master.TicketQueue) []*master.LoketTiketResponse { ticketMap := make(map[int64]*master.LoketTiketResponse) for _, ticket := range tickets { // Jika ticket ID belum ada di map, buat entry baru if _, exists := ticketMap[ticket.ID]; !exists { ticketNumber := ticket.Ticket if ticket.HealthcareServiceCode.Valid && ticket.HealthcareServiceCode.String != "" { ticketNumber = ticket.HealthcareServiceCode.String + ticket.Ticket } klinikName := "" if ticket.HealthcareServiceName.Valid { klinikName = ticket.HealthcareServiceName.String } shift := 0 if ticket.ShiftNumber.Valid { shift = int(ticket.ShiftNumber.Int64) } payment := "" if ticket.PaymentTypeName.Valid { payment = ticket.PaymentTypeName.String } ticketMap[ticket.ID] = &master.LoketTiketResponse{ IdLoketTiket: fmt.Sprintf("%d", ticket.ID), TiketLoket: ticketNumber, KlinikTiketLoket: klinikName, BarcodeTiketLoket: ticket.VisitCode, TanggalTiketLoket: ticket.DatetimeStart.Format("2006-01-02"), WaktuTiketLoket: ticket.DatetimeStart.Format("15:04:05"), ShiftTiketLoket: shift, Pembayaran: payment, Posisi: []master.Posisi{}, } } // Tambahkan posisi ke array (jika ada data posisi) if (ticket.Posisi.Valid && ticket.Posisi.String != "") || (ticket.Deskripsi.Valid && ticket.Deskripsi.String != "") || (ticket.WaktuPosisi.Valid) { posisiData := master.Posisi{} if ticket.Posisi.Valid { posisiData.Posisi = ticket.Posisi.String } if ticket.Deskripsi.Valid { posisiData.Deskripsi = ticket.Deskripsi.String } if ticket.WaktuPosisi.Valid { posisiData.TanggalPosisi = ticket.WaktuPosisi.Time.Format("2006-01-02") posisiData.WaktuPosisi = ticket.WaktuPosisi.Time.Format("15:04:05") } ticketMap[ticket.ID].Posisi = append(ticketMap[ticket.ID].Posisi, posisiData) } } // Convert map ke slice result := make([]*master.LoketTiketResponse, 0, len(ticketMap)) for _, ticket := range ticketMap { result = append(result, ticket) } return result } func (h *LoketHandler) GetRemainingQuota(ctx context.Context, dbConn *sqlx.DB, counterID string) (int, error) { currentDate := time.Now().Format("2006-01-02") query := ` SELECT mrc.quota, COALESCE(tmrcsq.seat_quota, 0) as used_quota FROM master.ms_registration_counter mrc LEFT JOIN temporary.tm_registration_counter_seat_quota tmrcsq ON tmrcsq.fk_ms_registration_counter_id = mrc.id AND tmrcsq.date = $1 WHERE mrc.id = $2 ` var maxQuota, usedQuota int err := dbConn.QueryRowContext(ctx, query, currentDate, counterID).Scan(&maxQuota, &usedQuota) if err != nil { if err == sql.ErrNoRows { return 0, fmt.Errorf("loket not found") } return 0, fmt.Errorf("failed to get quota: %w", err) } remainingQuota := maxQuota - usedQuota logger.Info("Remaining quota retrieved", map[string]interface{}{ "counter_id": counterID, "max_quota": maxQuota, "used_quota": usedQuota, "remaining_quota": remainingQuota, }) return remainingQuota, nil } func (h *LoketHandler) GetKlinikRuang(c *gin.Context) { ctx, cancel := context.WithTimeout(c.Request.Context(), 120*time.Second) defer cancel() dbConn, err := h.db.GetSQLXDB("postgres_antrean") if err != nil { logger.Error("Database connection failed", map[string]interface{}{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": "Database connection failed"}) return } // Get data klinik dan ruangan listKlinikRuangan, err := h.GetRuang(ctx, dbConn) if err != nil { if err == sql.ErrNoRows { c.JSON(http.StatusOK, master.KlinikRuanganResponse{ Message: "Data klinik dan ruangan tidak ditemukan", Data: []*master.KlinikRuanganGrouped{}, Meta: map[string]interface{}{ "total": 0, }, }) return } logger.Error("Failed to get klinik ruangan", map[string]interface{}{"error": err.Error()}) c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve data"}) return } // Group by klinik groupedData := h.groupByKlinikRuangan(listKlinikRuangan) c.JSON(http.StatusOK, master.KlinikRuanganResponse{ Message: "Data klinik dan ruangan berhasil diambil", Data: groupedData, Meta: map[string]interface{}{ "total": len(groupedData), }, }) } func (h *LoketHandler) GetRuang(ctx context.Context, dbConn *sqlx.DB) ([]*master.KlinikRuangan, error) { query := ` SELECT mhs.id as "idklinik", mhs.name as "namaklinik", mshs.id as "idruangan", mshs.name as "namaruangan" FROM master.ms_healthcare_service mhs LEFT JOIN master.ms_sub_healthcare_service mshs ON mshs.fk_ms_healthcare_service_id = mhs.id ORDER BY mhs.id, mshs.id ` var listKlinikRuangan []*master.KlinikRuangan err := dbConn.SelectContext(ctx, &listKlinikRuangan, query) if err != nil { return nil, fmt.Errorf("failed to get klinik ruangan: %w", err) } if len(listKlinikRuangan) == 0 { return nil, sql.ErrNoRows } logger.Info("Klinik ruangan retrieved successfully", map[string]interface{}{ "total": len(listKlinikRuangan), }) return listKlinikRuangan, nil } func (h *LoketHandler) groupByKlinikRuangan(data []*master.KlinikRuangan) []*master.KlinikRuanganGrouped { klinikMap := make(map[string]*master.KlinikRuanganGrouped) ruanganMap := make(map[string]map[string]bool) for _, item := range data { if !item.IDKlinik.Valid || item.IDKlinik.String == "" { continue } idKlinik := item.IDKlinik.String if _, exists := klinikMap[idKlinik]; !exists { namaKlinik := "" if item.NamaKlinik.Valid { namaKlinik = item.NamaKlinik.String } klinikMap[idKlinik] = &master.KlinikRuanganGrouped{ IDKlinik: idKlinik, NamaKlinik: namaKlinik, Ruangan: []master.RuanganDetail{}, } ruanganMap[idKlinik] = make(map[string]bool) } if item.IDRuangan.Valid && item.IDRuangan.String != "" { idRuangan := item.IDRuangan.String if !ruanganMap[idKlinik][idRuangan] { namaRuangan := "" if item.NamaRuangan.Valid { namaRuangan = item.NamaRuangan.String } ruanganDetail := master.RuanganDetail{ IDRuangan: idRuangan, NamaRuangan: namaRuangan, } klinikMap[idKlinik].Ruangan = append(klinikMap[idKlinik].Ruangan, ruanganDetail) ruanganMap[idKlinik][idRuangan] = true } } } result := make([]*master.KlinikRuanganGrouped, 0, len(klinikMap)) for _, klinik := range klinikMap { result = append(result, klinik) } return result }