Files
2026-02-13 15:57:28 +07:00

404 lines
12 KiB
Go

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
}