950 lines
27 KiB
Go
950 lines
27 KiB
Go
package master
|
|
|
|
import (
|
|
"api-service/internal/config"
|
|
"api-service/internal/database"
|
|
"api-service/internal/models"
|
|
modelsMaster "api-service/internal/models/master"
|
|
queryUtils "api-service/internal/utils/query"
|
|
"api-service/internal/utils/validation"
|
|
"api-service/pkg/logger"
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/go-playground/validator/v10"
|
|
"github.com/jmoiron/sqlx"
|
|
"log"
|
|
"net/http"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
db database.Service
|
|
once sync.Once
|
|
validate *validator.Validate
|
|
)
|
|
|
|
func init() {
|
|
once.Do(func() {
|
|
db = database.New(config.LoadConfig())
|
|
validate = validator.New()
|
|
validate.RegisterValidation("satudata_status", validated)
|
|
|
|
if db == nil {
|
|
logger.Fatal("Failed to initialize database connection")
|
|
}
|
|
})
|
|
}
|
|
|
|
func validated(fl validator.FieldLevel) bool {
|
|
return models.IsValidStatus(fl.Field().String())
|
|
}
|
|
|
|
func NewPoliHandler() *PoliHandler {
|
|
// Initialize QueryBuilder with allowed columns list for security.
|
|
queryBuilder := queryUtils.NewQueryBuilder(queryUtils.DBTypePostgreSQL).
|
|
SetAllowedColumns([]string{
|
|
"mrc.id",
|
|
"mrc.name",
|
|
"mrc.code",
|
|
"mrc.quota",
|
|
"mrc.active",
|
|
"mrc.fk_ref_healthcare_type_id",
|
|
"mrc.fk_ref_service_type_id",
|
|
|
|
"rht.id",
|
|
"rht.name",
|
|
|
|
"rst.id",
|
|
"rst.name",
|
|
|
|
"mrcvt.id",
|
|
"mrcvt.fk_ms_registration_counter_id",
|
|
"mrcvt.fk_ref_visit_type_id",
|
|
|
|
"rvt.id",
|
|
"rvt.name",
|
|
|
|
"mrcpt.id",
|
|
"mrcpt.fk_ms_registration_counter_id",
|
|
"mrcpt.fk_ref_payment_type_id",
|
|
|
|
"rpt.id",
|
|
"rpt.name",
|
|
|
|
"mrchs.id",
|
|
"mrchs.fk_ms_registration_counter_id",
|
|
"mrchs.fk_ms_healthcare_service_id",
|
|
"mhss2.fk_ms_healthcare_service_id",
|
|
"master.ms_healthcare_service_shift",
|
|
"fk_ms_registration_counter_id",
|
|
"master.ms_healthcare_service_schedule",
|
|
"mhss3.fk_ms_healthcare_service_id",
|
|
|
|
"mhs.id",
|
|
"mhs.name",
|
|
|
|
"mhss2",
|
|
"mhss2.quota",
|
|
"mhss2.shift",
|
|
|
|
"namaloket",
|
|
"kodeloket",
|
|
"kuotaloket",
|
|
"loketaktif",
|
|
"jenisloket",
|
|
"tipeloket",
|
|
"tipevisit",
|
|
"pembayaran",
|
|
"namaklinik",
|
|
"idpayment",
|
|
|
|
"id",
|
|
"name",
|
|
"code",
|
|
"quota",
|
|
"active",
|
|
"shift",
|
|
"kuota",
|
|
})
|
|
|
|
return &PoliHandler{
|
|
db: db,
|
|
queryBuilder: queryBuilder,
|
|
validator: validation.NewDynamicValidator(queryBuilder),
|
|
}
|
|
}
|
|
|
|
type PoliHandler struct {
|
|
db database.Service
|
|
queryBuilder *queryUtils.QueryBuilder
|
|
validator *validation.DynamicValidator
|
|
}
|
|
|
|
func (h *PoliHandler) GetAnjungan(c *gin.Context) {
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 120*time.Second)
|
|
defer cancel()
|
|
|
|
req := c.Param("jenis")
|
|
|
|
var tipeKlinik string
|
|
|
|
switch strings.ToLower(req) {
|
|
case "reguler", "REGULER":
|
|
tipeKlinik = "KLINIK REGULER"
|
|
case "vip":
|
|
tipeKlinik = "KLINIK VIP"
|
|
case "eksekutif", "EKSEKUTIF":
|
|
tipeKlinik = "KLINIK EKSEKUTIF"
|
|
default:
|
|
}
|
|
|
|
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
|
|
}
|
|
currentDate := time.Now().Format("2006-01-02")
|
|
|
|
query := queryUtils.DynamicQuery{
|
|
From: "master.ms_kiosk",
|
|
Aliases: "mk",
|
|
Fields: []queryUtils.SelectField{
|
|
{Expression: "mk.name", Alias: "tipeanjungan"},
|
|
{Expression: "mk.active", Alias: "active"},
|
|
{Expression: "mhs.id", Alias: "idklinik"},
|
|
{Expression: "mhs.name", Alias: "namaklinik"},
|
|
{Expression: "mhs.code", Alias: "code"},
|
|
{Expression: "mhss.ds_sd_spesialis", Alias: "spesialis"},
|
|
{Expression: "mhss.fk_sd_spesialis_id", Alias: "idspesialis"},
|
|
{Expression: "mks.day_of_week", Alias: "hari"},
|
|
{Expression: "mhss3.available_start_time", Alias: "mulai"},
|
|
{Expression: "mhss3.available_end_time", Alias: "selesai"},
|
|
{Expression: "mks.available_start_time", Alias: "mulaianjungan"},
|
|
{Expression: "mks.available_end_time", Alias: "selesaianjungan"},
|
|
{Expression: "mhss2.shift_number", Alias: "shift"},
|
|
{Expression: "mhss2.quota", Alias: "kuota"},
|
|
{Expression: "mrc.id", Alias: "idloket"},
|
|
{Expression: "mrc.name", Alias: "namaloket"},
|
|
{Expression: "mrc.code", Alias: "kodeloket"},
|
|
{Expression: "mrc.quota", Alias: "quota_loket"},
|
|
{Expression: "(mrc.quota - COALESCE(tmrcsq.seat_quota, 0))", Alias: "available_quota"},
|
|
{Expression: "rpt.id", Alias: "idpayment"},
|
|
{Expression: "rpt.name", Alias: "paymenttype"},
|
|
},
|
|
Joins: []queryUtils.Join{
|
|
{
|
|
Type: "LEFT",
|
|
Table: "master.ms_kiosk_healthcare_service",
|
|
Alias: "mkhs",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mk.id", Operator: queryUtils.OpEqual, Value: "mkhs.fk_ms_kiosk_id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "LEFT",
|
|
Table: "master.ms_healthcare_service",
|
|
Alias: "mhs",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mkhs.fk_ms_healthcare_service_id", Operator: queryUtils.OpEqual, Value: "mhs.id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "LEFT",
|
|
Table: "master.ms_healthcare_service_spesialis",
|
|
Alias: "mhss",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mhs.id", Operator: queryUtils.OpEqual, Value: "mhss.fk_ms_healthcare_service_id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "LEFT",
|
|
Table: "master.ms_kiosk_schedule",
|
|
Alias: "mks",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mks.fk_ms_kiosk_id", Operator: queryUtils.OpEqual, Value: "mk.id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "LEFT",
|
|
Table: "master.ms_healthcare_service_shift",
|
|
Alias: "mhss2",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mhss2.fk_ms_healthcare_service_id", Operator: queryUtils.OpEqual, Value: "mkhs.id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "LEFT",
|
|
Table: "master.ms_registration_counter_healthcare_service",
|
|
Alias: "mrchs",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mrchs.fk_ms_healthcare_service_id", Operator: queryUtils.OpEqual, Value: "mhs.id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "LEFT",
|
|
Table: "master.ms_registration_counter",
|
|
Alias: "mrc",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "fk_ms_registration_counter_id", Operator: queryUtils.OpEqual, Value: "mrc.id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "LEFT",
|
|
Table: "temporary.tm_registration_counter_seat_quota",
|
|
Alias: "tmrcsq",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "tmrcsq.fk_ms_registration_counter_id", Operator: queryUtils.OpEqual, Value: "mrc.id"},
|
|
{Column: "tmrcsq.date", Operator: queryUtils.OpEqual, Value: currentDate},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "LEFT",
|
|
Table: "master.ms_healthcare_service_schedule",
|
|
Alias: "mhss3",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mhss3.fk_ms_healthcare_service_id", Operator: queryUtils.OpEqual, Value: "mhs.id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "INNER",
|
|
Table: "master.ms_kiosk_payment_type",
|
|
Alias: "mkpt",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mkpt.fk_ms_kiosk_id", Operator: queryUtils.OpEqual, Value: "mk.id"},
|
|
},
|
|
},
|
|
},
|
|
|
|
{
|
|
Type: "INNER",
|
|
Table: "master.ms_healthcare_service_payment_type",
|
|
Alias: "mhspt",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mhspt.fk_ms_healthcare_service_id", Operator: queryUtils.OpEqual, Value: "mhs.id"},
|
|
{Column: "mkpt.fk_ref_payment_type_id", Operator: queryUtils.OpEqual, Value: "mhspt.fk_ref_payment_type_id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "INNER",
|
|
Table: "reference.ref_payment_type",
|
|
Alias: "rpt",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mkpt.fk_ref_payment_type_id", Operator: queryUtils.OpEqual, Value: "rpt.id"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
Filters: []queryUtils.FilterGroup{
|
|
{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{
|
|
Column: "mk.name",
|
|
Operator: queryUtils.OpEqual,
|
|
Value: tipeKlinik,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
log.Println(query)
|
|
|
|
ListPoli, total, err := h.GetdataSpesialis(ctx, dbConn, query)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
c.JSON(http.StatusOK, modelsMaster.PoliklinikResponse{
|
|
Message: "Data spesialis tidak ditemukan",
|
|
Data: []*modelsMaster.PoliklinikGrouped{},
|
|
Meta: map[string]interface{}{
|
|
"total": 0,
|
|
"count": 0,
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
logger.Error("Failed to get spesialis data", map[string]interface{}{"error": err.Error()})
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve data"})
|
|
return
|
|
}
|
|
|
|
groupedData := h.groupByKlinik(ListPoli)
|
|
|
|
response := modelsMaster.PoliklinikResponse{
|
|
Message: "Data Poli berhasil diambil",
|
|
Data: groupedData,
|
|
Meta: map[string]interface{}{
|
|
"total": total,
|
|
"count": len(ListPoli),
|
|
},
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
func (h *PoliHandler) GetdataSpesialis(ctx context.Context, dbConn *sqlx.DB, query queryUtils.DynamicQuery) ([]*modelsMaster.ListPoli, int, error) {
|
|
var List []*modelsMaster.ListPoli
|
|
|
|
queryCtx, queryCancel := context.WithTimeout(ctx, 30*time.Second)
|
|
defer queryCancel()
|
|
|
|
logger.Info("Executing spesialis query")
|
|
|
|
err := h.queryBuilder.ExecuteQuery(queryCtx, dbConn, query, &List)
|
|
if err != nil {
|
|
logger.Error("Failed to execute spesialis query", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"query": query,
|
|
})
|
|
return nil, 0, fmt.Errorf("failed to execute spesialis query: %w", err)
|
|
}
|
|
|
|
total := len(List)
|
|
|
|
logger.Info("Query execution completed", map[string]interface{}{
|
|
"totalRecords": total,
|
|
"returnedRecords": len(List),
|
|
})
|
|
|
|
// Check if no data found
|
|
if total == 0 {
|
|
return nil, 0, sql.ErrNoRows
|
|
}
|
|
|
|
return List, total, nil
|
|
}
|
|
|
|
//func (h *PoliHandler) groupByKlinik(data []*modelsMaster.ListPoli) []*modelsMaster.PoliklinikGrouped {
|
|
// klinikMap := make(map[string]*modelsMaster.PoliklinikGrouped)
|
|
// spesialisMap := make(map[string]map[int]bool)
|
|
// jadwalMap := make(map[string]map[string]bool)
|
|
// loketMap := make(map[string]map[string]bool)
|
|
// paymentMap := make(map[string]map[string]bool)
|
|
//
|
|
// for _, item := range data {
|
|
// // Skip jika IDKlinik tidak valid atau kosong
|
|
// if !item.IDKlinik.Valid || item.IDKlinik.String == "" {
|
|
// continue
|
|
// }
|
|
//
|
|
// idKlinik := item.IDKlinik.String
|
|
//
|
|
// // Jika klinik belum ada di map, buat entry baru
|
|
// if _, exists := klinikMap[idKlinik]; !exists {
|
|
// namaKlinik := ""
|
|
// if item.NamaKlinik.Valid {
|
|
// namaKlinik = item.NamaKlinik.String
|
|
// }
|
|
//
|
|
// klinikMap[idKlinik] = &modelsMaster.PoliklinikGrouped{
|
|
// TipeAnjungan: item.TipeAnjungan,
|
|
// Active: item.Active,
|
|
// IDKlinik: idKlinik,
|
|
// NamaKlinik: namaKlinik,
|
|
// Shift: item.Shift,
|
|
// Kuota: item.Kuota,
|
|
// Code: item.Code,
|
|
// Jadwal: []modelsMaster.ScheduleDetail{},
|
|
// Spesialis: []modelsMaster.SpesialisDetail{},
|
|
// Loket: []modelsMaster.LoketDetail{},
|
|
// Payment: []modelsMaster.PaymentDetail{},
|
|
// }
|
|
//
|
|
// // Inisialisasi map untuk tracking
|
|
// spesialisMap[idKlinik] = make(map[int]bool)
|
|
// jadwalMap[idKlinik] = make(map[string]bool)
|
|
// loketMap[idKlinik] = make(map[string]bool)
|
|
// paymentMap[idKlinik] = make(map[string]bool)
|
|
// }
|
|
//
|
|
// // Tambahkan jadwal jika belum ada
|
|
// if item.Hari != "" && item.JamMulai != "" && item.JamSelesai != "" {
|
|
// jamOperasional := fmt.Sprintf("%s - %s", item.JamMulai, item.JamSelesai)
|
|
// jadwalKey := fmt.Sprintf("%s_%s", item.Hari, jamOperasional)
|
|
//
|
|
// // Cek apakah jadwal dengan hari dan jam operasional ini sudah ada
|
|
// if !jadwalMap[idKlinik][jadwalKey] {
|
|
// schedule := modelsMaster.ScheduleDetail{
|
|
// Hari: item.Hari,
|
|
// JamOperasional: jamOperasional,
|
|
// }
|
|
//
|
|
// klinikMap[idKlinik].Jadwal = append(klinikMap[idKlinik].Jadwal, schedule)
|
|
// jadwalMap[idKlinik][jadwalKey] = true
|
|
// }
|
|
// }
|
|
//
|
|
// // Tambahkan spesialis jika belum ada
|
|
// if item.IDSpesialis.Valid && item.IDSpesialis.Int64 != 0 && item.Spesialis.Valid && item.Spesialis.String != "" {
|
|
// idSpesialis := int(item.IDSpesialis.Int64)
|
|
//
|
|
// if !spesialisMap[idKlinik][idSpesialis] {
|
|
// spesialisDetail := modelsMaster.SpesialisDetail{
|
|
// IDSpesialis: idSpesialis,
|
|
// Spesialis: item.Spesialis.String,
|
|
// }
|
|
//
|
|
// klinikMap[idKlinik].Spesialis = append(klinikMap[idKlinik].Spesialis, spesialisDetail)
|
|
// spesialisMap[idKlinik][idSpesialis] = true
|
|
// }
|
|
// }
|
|
//
|
|
// if item.IDloket.Valid && item.IDloket.String != "" {
|
|
// idLoket := item.IDloket.String
|
|
//
|
|
// if !loketMap[idKlinik][idLoket] {
|
|
// namaLoket := ""
|
|
// if item.Namaloket.Valid {
|
|
// namaLoket = item.Namaloket.String
|
|
// }
|
|
//
|
|
// kodeLoket := ""
|
|
// if item.KodeLoket.Valid {
|
|
// kodeLoket = item.KodeLoket.String
|
|
// }
|
|
//
|
|
// loketDetail := modelsMaster.LoketDetail{
|
|
// IDLoket: idLoket,
|
|
// NamaLoket: namaLoket,
|
|
// KodeLoket: kodeLoket,
|
|
// }
|
|
//
|
|
// klinikMap[idKlinik].Loket = append(klinikMap[idKlinik].Loket, loketDetail)
|
|
// loketMap[idKlinik][idLoket] = true
|
|
// }
|
|
// }
|
|
// if item.PaymentType.Valid && item.PaymentType.String != "" {
|
|
// idPayment := int(item.IdPayment.Int64)
|
|
// paymentType := item.PaymentType.String
|
|
//
|
|
// if !paymentMap[idKlinik][paymentType] {
|
|
// paymentDetail := modelsMaster.PaymentDetail{
|
|
// IDPayment: idPayment,
|
|
// PaymentType: paymentType,
|
|
// }
|
|
//
|
|
// klinikMap[idKlinik].Payment = append(klinikMap[idKlinik].Payment, paymentDetail)
|
|
// paymentMap[idKlinik][paymentType] = true
|
|
// }
|
|
// }
|
|
// }
|
|
//
|
|
// result := make([]*modelsMaster.PoliklinikGrouped, 0, len(klinikMap))
|
|
// for _, klinik := range klinikMap {
|
|
// result = append(result, klinik)
|
|
// }
|
|
//
|
|
// return result
|
|
//}
|
|
|
|
func (h *PoliHandler) groupByKlinik(data []*modelsMaster.ListPoli) []*modelsMaster.PoliklinikGrouped {
|
|
klinikMap := make(map[string]*modelsMaster.PoliklinikGrouped)
|
|
spesialisMap := make(map[string]map[int]bool)
|
|
jadwalMap := make(map[string]map[string]bool)
|
|
loketMap := make(map[string][]modelsMaster.LoketDetail) // ✅ Array untuk menyimpan semua loket
|
|
paymentMap := make(map[string]map[string]bool)
|
|
|
|
for _, item := range data {
|
|
// Skip jika IDKlinik tidak valid atau kosong
|
|
if !item.IDKlinik.Valid || item.IDKlinik.String == "" {
|
|
continue
|
|
}
|
|
|
|
idKlinik := item.IDKlinik.String
|
|
|
|
// Jika klinik belum ada di map, buat entry baru
|
|
if _, exists := klinikMap[idKlinik]; !exists {
|
|
namaKlinik := ""
|
|
if item.NamaKlinik.Valid {
|
|
namaKlinik = item.NamaKlinik.String
|
|
}
|
|
|
|
klinikMap[idKlinik] = &modelsMaster.PoliklinikGrouped{
|
|
TipeAnjungan: item.TipeAnjungan,
|
|
Active: item.Active,
|
|
IDKlinik: idKlinik,
|
|
NamaKlinik: namaKlinik,
|
|
Shift: item.Shift,
|
|
Kuota: item.Kuota,
|
|
Code: item.Code,
|
|
Jadwal: []modelsMaster.ScheduleDetail{},
|
|
Spesialis: []modelsMaster.SpesialisDetail{},
|
|
Loket: []modelsMaster.LoketDetail{},
|
|
Payment: []modelsMaster.PaymentDetail{},
|
|
}
|
|
|
|
// Inisialisasi map untuk tracking
|
|
spesialisMap[idKlinik] = make(map[int]bool)
|
|
jadwalMap[idKlinik] = make(map[string]bool)
|
|
loketMap[idKlinik] = []modelsMaster.LoketDetail{}
|
|
paymentMap[idKlinik] = make(map[string]bool)
|
|
}
|
|
|
|
// Tambahkan jadwal jika belum ada
|
|
if item.Hari != "" && item.JamMulai != "" && item.JamSelesai != "" {
|
|
jamOperasional := fmt.Sprintf("%s - %s", item.JamMulai, item.JamSelesai)
|
|
jadwalKey := fmt.Sprintf("%s_%s", item.Hari, jamOperasional)
|
|
|
|
if !jadwalMap[idKlinik][jadwalKey] {
|
|
schedule := modelsMaster.ScheduleDetail{
|
|
Hari: item.Hari,
|
|
JamOperasional: jamOperasional,
|
|
}
|
|
|
|
klinikMap[idKlinik].Jadwal = append(klinikMap[idKlinik].Jadwal, schedule)
|
|
jadwalMap[idKlinik][jadwalKey] = true
|
|
}
|
|
}
|
|
|
|
// Tambahkan spesialis jika belum ada
|
|
if item.IDSpesialis.Valid && item.IDSpesialis.Int64 != 0 && item.Spesialis.Valid && item.Spesialis.String != "" {
|
|
idSpesialis := int(item.IDSpesialis.Int64)
|
|
|
|
if !spesialisMap[idKlinik][idSpesialis] {
|
|
spesialisDetail := modelsMaster.SpesialisDetail{
|
|
IDSpesialis: idSpesialis,
|
|
Spesialis: item.Spesialis.String,
|
|
}
|
|
|
|
klinikMap[idKlinik].Spesialis = append(klinikMap[idKlinik].Spesialis, spesialisDetail)
|
|
spesialisMap[idKlinik][idSpesialis] = true
|
|
}
|
|
}
|
|
|
|
// ✅ LOGIC BARU: Kumpulkan semua loket dulu
|
|
if item.IDloket.Valid && item.IDloket.String != "" {
|
|
idLoket := item.IDloket.String
|
|
|
|
// Cek apakah loket ini sudah ada di array
|
|
loketExists := false
|
|
for _, loket := range loketMap[idKlinik] {
|
|
if loket.IDLoket == idLoket {
|
|
loketExists = true
|
|
break
|
|
}
|
|
}
|
|
|
|
if !loketExists {
|
|
namaLoket := ""
|
|
if item.Namaloket.Valid {
|
|
namaLoket = item.Namaloket.String
|
|
}
|
|
|
|
kodeLoket := ""
|
|
if item.KodeLoket.Valid {
|
|
kodeLoket = item.KodeLoket.String
|
|
}
|
|
|
|
availableQuota := 0
|
|
if item.AvailableQuota.Valid {
|
|
availableQuota = int(item.AvailableQuota.Int64)
|
|
} else if item.QuotaLoket.Valid {
|
|
// Kalau available_quota NULL, berarti belum ada yang pakai (awal hari)
|
|
availableQuota = int(item.QuotaLoket.Int64)
|
|
}
|
|
|
|
loketDetail := modelsMaster.LoketDetail{
|
|
IDLoket: idLoket,
|
|
NamaLoket: namaLoket,
|
|
KodeLoket: kodeLoket,
|
|
AvailableQuota: availableQuota,
|
|
}
|
|
|
|
loketMap[idKlinik] = append(loketMap[idKlinik], loketDetail)
|
|
}
|
|
}
|
|
|
|
// Payment (tetap sama)
|
|
if item.PaymentType.Valid && item.PaymentType.String != "" {
|
|
idPayment := int(item.IdPayment.Int64)
|
|
paymentType := item.PaymentType.String
|
|
|
|
if !paymentMap[idKlinik][paymentType] {
|
|
paymentDetail := modelsMaster.PaymentDetail{
|
|
IDPayment: idPayment,
|
|
PaymentType: paymentType,
|
|
}
|
|
|
|
klinikMap[idKlinik].Payment = append(klinikMap[idKlinik].Payment, paymentDetail)
|
|
paymentMap[idKlinik][paymentType] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// ✅ LOGIC PEMILIHAN LOKET
|
|
for idKlinik, loketList := range loketMap {
|
|
if len(loketList) == 0 {
|
|
continue
|
|
}
|
|
|
|
// Cek apakah ada loket yang sudah terpakai (ada record quota hari ini)
|
|
adaYangTerpakai := false
|
|
for _, loket := range loketList {
|
|
// Kalau available_quota < quota_loket, berarti sudah ada yang pakai
|
|
for _, item := range data {
|
|
if item.IDKlinik.Valid && item.IDKlinik.String == idKlinik &&
|
|
item.IDloket.Valid && item.IDloket.String == loket.IDLoket {
|
|
if item.AvailableQuota.Valid && item.QuotaLoket.Valid {
|
|
if item.AvailableQuota.Int64 < item.QuotaLoket.Int64 {
|
|
adaYangTerpakai = true
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if adaYangTerpakai {
|
|
break
|
|
}
|
|
}
|
|
|
|
if adaYangTerpakai {
|
|
// ✅ Sudah ada yang pakai tiket: Pilih 1 loket dengan quota terbanyak
|
|
var selectedLoket *modelsMaster.LoketDetail
|
|
maxQuota := -1
|
|
|
|
for i := range loketList {
|
|
if loketList[i].AvailableQuota > maxQuota {
|
|
maxQuota = loketList[i].AvailableQuota
|
|
selectedLoket = &loketList[i]
|
|
}
|
|
}
|
|
|
|
if selectedLoket != nil {
|
|
klinikMap[idKlinik].Loket = []modelsMaster.LoketDetail{*selectedLoket}
|
|
}
|
|
} else {
|
|
// ✅ Awal hari (belum ada yang pakai): Tampilkan semua loket
|
|
klinikMap[idKlinik].Loket = loketList
|
|
}
|
|
}
|
|
|
|
result := make([]*modelsMaster.PoliklinikGrouped, 0, len(klinikMap))
|
|
for _, klinik := range klinikMap {
|
|
result = append(result, klinik)
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
func (h *PoliHandler) GetLoket(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
|
|
}
|
|
|
|
query := queryUtils.DynamicQuery{
|
|
From: "master.ms_registration_counter",
|
|
Aliases: "mrc",
|
|
Fields: []queryUtils.SelectField{
|
|
{Expression: "mrc.id", Alias: "idloket"},
|
|
{Expression: "mrc.name", Alias: "namaloket"},
|
|
{Expression: "mrc.code", Alias: "kodeloket"},
|
|
{Expression: "mrc.quota", Alias: "kuotaloket"},
|
|
{Expression: "mrc.active", Alias: "loketaktif"},
|
|
{Expression: "rht.name", Alias: "jenisloket"},
|
|
{Expression: "rst.name", Alias: "tipeloket"},
|
|
{Expression: "rvt.name", Alias: "tipevisit"},
|
|
{Expression: "rpt.name", Alias: "pembayaran"},
|
|
{Expression: "mhs.name", Alias: "namaklinik"},
|
|
{Expression: "mhs.id", Alias: "idklinik"},
|
|
},
|
|
Joins: []queryUtils.Join{
|
|
{
|
|
Type: "LEFT",
|
|
Table: "reference.ref_healthcare_type",
|
|
Alias: "rht",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mrc.fk_ref_service_type_id", Operator: queryUtils.OpEqual, Value: "rht.id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "LEFT",
|
|
Table: "reference.ref_service_type",
|
|
Alias: "rst",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mrc.fk_ref_service_type_id", Operator: queryUtils.OpEqual, Value: "rst.id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "INNER",
|
|
Table: "master.ms_registration_counter_visit_type",
|
|
Alias: "mrcvt",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mrcvt.fk_ms_registration_counter_id", Operator: queryUtils.OpEqual, Value: "mrc.id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "LEFT",
|
|
Table: "reference.ref_visit_type",
|
|
Alias: "rvt",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mrcvt.fk_ref_visit_type_id", Operator: queryUtils.OpEqual, Value: "rvt.id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "INNER",
|
|
Table: "master.ms_registration_counter_payment_type",
|
|
Alias: "mrcpt",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mrcpt.fk_ms_registration_counter_id", Operator: queryUtils.OpEqual, Value: "mrc.id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "LEFT",
|
|
Table: "reference.ref_payment_type",
|
|
Alias: "rpt",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mrcpt.fk_ref_payment_type_id", Operator: queryUtils.OpEqual, Value: "rpt.id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "INNER",
|
|
Table: "master.ms_registration_counter_healthcare_service",
|
|
Alias: "mrchs",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mrchs.fk_ms_registration_counter_id", Operator: queryUtils.OpEqual, Value: "mrc.id"},
|
|
},
|
|
},
|
|
},
|
|
{
|
|
Type: "LEFT",
|
|
Table: "master.ms_healthcare_service",
|
|
Alias: "mhs",
|
|
OnConditions: queryUtils.FilterGroup{
|
|
Filters: []queryUtils.DynamicFilter{
|
|
{Column: "mrchs.fk_ms_healthcare_service_id", Operator: queryUtils.OpEqual, Value: "mhs.id"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
log.Println(query)
|
|
|
|
ListLoket, total, err := h.GetdataLoket(ctx, dbConn, query)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
c.JSON(http.StatusOK, modelsMaster.LoketResponse{
|
|
Message: "Data loket tidak ditemukan",
|
|
Data: []*modelsMaster.LoketGrouped{},
|
|
Meta: map[string]interface{}{
|
|
"total": 0,
|
|
"count": 0,
|
|
},
|
|
})
|
|
return
|
|
}
|
|
|
|
logger.Error("Failed to get loket data", map[string]interface{}{"error": err.Error()})
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to retrieve data"})
|
|
return
|
|
}
|
|
|
|
// GROUPING DATA DISINI
|
|
groupedData := h.groupByLoket(ListLoket)
|
|
|
|
response := modelsMaster.LoketResponse{
|
|
Message: "Data Loket berhasil diambil",
|
|
Data: groupedData,
|
|
Meta: map[string]interface{}{
|
|
"total_loket": len(groupedData),
|
|
"total_records": total,
|
|
},
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
func (h *PoliHandler) GetdataLoket(ctx context.Context, dbConn *sqlx.DB, query queryUtils.DynamicQuery) ([]*modelsMaster.ListLoket, int, error) {
|
|
var List []*modelsMaster.ListLoket
|
|
|
|
queryCtx, queryCancel := context.WithTimeout(ctx, 30*time.Second)
|
|
defer queryCancel()
|
|
|
|
logger.Info("Executing loket query")
|
|
|
|
err := h.queryBuilder.ExecuteQuery(queryCtx, dbConn, query, &List)
|
|
if err != nil {
|
|
logger.Error("Failed to execute loket query", map[string]interface{}{
|
|
"error": err.Error(),
|
|
"query": query,
|
|
})
|
|
return nil, 0, fmt.Errorf("failed to execute loket query: %w", err)
|
|
}
|
|
|
|
total := len(List)
|
|
|
|
logger.Info("Query execution completed", map[string]interface{}{
|
|
"totalRecords": total,
|
|
"returnedRecords": len(List),
|
|
})
|
|
|
|
if total == 0 {
|
|
return nil, 0, sql.ErrNoRows
|
|
}
|
|
|
|
return List, total, nil
|
|
}
|
|
|
|
// FUNGSI UNTUK GROUPING - Group by Loket
|
|
func (h *PoliHandler) groupByLoket(data []*modelsMaster.ListLoket) []*modelsMaster.LoketGrouped {
|
|
loketMap := make(map[string]*modelsMaster.LoketGrouped)
|
|
visitMap := make(map[string]map[string]bool)
|
|
klinikMap := make(map[string]map[string]bool) // Ubah jadi tracking by IDKlinik
|
|
pembayaranMap := make(map[string]map[string]bool)
|
|
|
|
for _, item := range data {
|
|
if item.KodeLoket == "" {
|
|
continue
|
|
}
|
|
|
|
// Jika loket belum ada di map, buat entry baru
|
|
if _, exists := loketMap[item.KodeLoket]; !exists {
|
|
loketMap[item.KodeLoket] = &modelsMaster.LoketGrouped{
|
|
IdLoket: item.IdLoket,
|
|
TipeAnjungan: item.NamaLoket,
|
|
KodeLoket: item.KodeLoket,
|
|
KuotaLoket: item.KuotaLoket,
|
|
LoketAktif: item.LoketAktif,
|
|
JenisLoket: item.JenisLoket,
|
|
TipeLoket: item.TipeLoket,
|
|
TipeVisit: []modelsMaster.ListVisit{},
|
|
NamaKlinik: []modelsMaster.ListKlinik{},
|
|
NamaPembayaran: []modelsMaster.ListPembayaran{},
|
|
}
|
|
|
|
// Inisialisasi map untuk tracking
|
|
visitMap[item.KodeLoket] = make(map[string]bool)
|
|
klinikMap[item.KodeLoket] = make(map[string]bool)
|
|
pembayaranMap[item.KodeLoket] = make(map[string]bool)
|
|
}
|
|
|
|
if item.TipeVisit != "" {
|
|
if !visitMap[item.KodeLoket][item.TipeVisit] {
|
|
visit := modelsMaster.ListVisit{
|
|
TipeVisit: item.TipeVisit,
|
|
}
|
|
|
|
loketMap[item.KodeLoket].TipeVisit = append(loketMap[item.KodeLoket].TipeVisit, visit)
|
|
visitMap[item.KodeLoket][item.TipeVisit] = true
|
|
}
|
|
}
|
|
|
|
if item.IDKlinik != "" && item.NamaKlinik != "" {
|
|
if !klinikMap[item.KodeLoket][item.IDKlinik] {
|
|
klinik := modelsMaster.ListKlinik{
|
|
IDKlinik: item.IDKlinik,
|
|
NamaKlinik: item.NamaKlinik,
|
|
}
|
|
|
|
loketMap[item.KodeLoket].NamaKlinik = append(loketMap[item.KodeLoket].NamaKlinik, klinik)
|
|
klinikMap[item.KodeLoket][item.IDKlinik] = true
|
|
}
|
|
}
|
|
|
|
if item.Pembayaran != "" {
|
|
if !pembayaranMap[item.KodeLoket][item.Pembayaran] {
|
|
pembayaran := modelsMaster.ListPembayaran{
|
|
NamaPembayaran: item.Pembayaran,
|
|
}
|
|
|
|
loketMap[item.KodeLoket].NamaPembayaran = append(loketMap[item.KodeLoket].NamaPembayaran, pembayaran)
|
|
pembayaranMap[item.KodeLoket][item.Pembayaran] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
// Convert map ke slice
|
|
result := make([]*modelsMaster.LoketGrouped, 0, len(loketMap))
|
|
for _, loket := range loketMap {
|
|
result = append(result, loket)
|
|
}
|
|
|
|
return result
|
|
}
|