first commit
This commit is contained in:
No files matched your search
@@ -0,0 +1,228 @@
|
||||
package master
|
||||
|
||||
import (
|
||||
"api-service/internal/config"
|
||||
"api-service/internal/database"
|
||||
"api-service/internal/models"
|
||||
modelsMaster "api-service/internal/models/master"
|
||||
"api-service/pkg/logger"
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
once.Do(func() {
|
||||
db = database.New(config.LoadConfig())
|
||||
validate = validator.New()
|
||||
|
||||
// Register custom validations if needed
|
||||
validate.RegisterValidation("retribusi_status", validateRetribusiStatus)
|
||||
|
||||
if db == nil {
|
||||
logger.Fatal("Failed to initialize database connection")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func validateRetribusiStatus(fl validator.FieldLevel) bool {
|
||||
return models.IsValidStatus(fl.Field().String())
|
||||
}
|
||||
|
||||
type DokterHandler struct {
|
||||
db database.Service
|
||||
}
|
||||
|
||||
func NewDokterHandler() *DokterHandler {
|
||||
return &DokterHandler{
|
||||
db: db,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *DokterHandler) GetDokterbyspesialis(c *gin.Context) {
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 120*time.Second)
|
||||
defer cancel()
|
||||
|
||||
klinikIDStr := c.Param("idklinik")
|
||||
klinikID, err := strconv.Atoi(klinikIDStr)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid klinik ID"})
|
||||
return
|
||||
}
|
||||
|
||||
dbAntrean, err := h.db.GetSQLXDB("postgres_antrean")
|
||||
if err != nil {
|
||||
logger.Error("Failed to connect postgres_antrean", map[string]interface{}{"error": err.Error()})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database connection failed"})
|
||||
return
|
||||
}
|
||||
|
||||
spesialisIDs, err := h.GetSpesialisIDsByKlinik(ctx, dbAntrean, klinikID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database connection failed"})
|
||||
return
|
||||
}
|
||||
if len(spesialisIDs) == 0 {
|
||||
c.JSON(http.StatusOK, modelsMaster.DokterResponse{
|
||||
Message: "Tidak ada dokter untuk klinik ini",
|
||||
Data: []*modelsMaster.DokterFormatted{},
|
||||
Meta: map[string]interface{}{"total": 0},
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
logger.Info("Spesialis IDs found", map[string]interface{}{
|
||||
"klinikID": klinikID,
|
||||
"spesialisIDs": spesialisIDs,
|
||||
"count": len(spesialisIDs),
|
||||
})
|
||||
|
||||
dbConn, err := h.db.GetSQLXDB("postgres_satudata")
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Database connection failed"})
|
||||
return
|
||||
}
|
||||
|
||||
dokterList, err := h.GetDokterBySpesialisIDs(ctx, dbConn, spesialisIDs)
|
||||
if err != nil {
|
||||
logger.Error("Failed to get dokter data", map[string]interface{}{"error": err.Error()})
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get dokter data"})
|
||||
return
|
||||
}
|
||||
|
||||
formattedData := h.FormatDokterData(dokterList)
|
||||
|
||||
c.JSON(http.StatusOK, modelsMaster.DokterResponse{
|
||||
Message: "Data dokter berhasil diambil",
|
||||
Data: formattedData,
|
||||
Meta: map[string]interface{}{
|
||||
"total": len(formattedData),
|
||||
"klinikID": klinikID,
|
||||
"jumlahSpesialis": len(spesialisIDs),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *DokterHandler) GetSpesialisIDsByKlinik(ctx context.Context, dbConn *sqlx.DB, klinikID int) ([]int, error) {
|
||||
query := `
|
||||
SELECT fk_sd_spesialis_id
|
||||
FROM master.ms_healthcare_service_spesialis
|
||||
WHERE fk_ms_healthcare_service_id = $1
|
||||
`
|
||||
|
||||
var spesialisIDs []int
|
||||
err := dbConn.SelectContext(ctx, &spesialisIDs, query, klinikID)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get spesialis IDs: %w", err)
|
||||
}
|
||||
|
||||
return spesialisIDs, nil
|
||||
}
|
||||
|
||||
func (h *DokterHandler) FormatDokterData(data []*modelsMaster.ListDokter) []*modelsMaster.DokterFormatted {
|
||||
result := make([]*modelsMaster.DokterFormatted, 0, len(data))
|
||||
|
||||
for _, dokter := range data {
|
||||
namaLengkap := ""
|
||||
|
||||
if dokter.GelarDepan != "" {
|
||||
gelarDepanList := strings.Split(dokter.GelarDepan, "|")
|
||||
var formattedGelar []string
|
||||
|
||||
for _, gelar := range gelarDepanList {
|
||||
gelar = strings.TrimSpace(gelar)
|
||||
|
||||
if gelar != "" && !strings.HasSuffix(gelar, ".") {
|
||||
gelar += "."
|
||||
}
|
||||
|
||||
if gelar != "" {
|
||||
formattedGelar = append(formattedGelar, gelar)
|
||||
}
|
||||
}
|
||||
|
||||
if len(formattedGelar) > 0 {
|
||||
namaLengkap = strings.Join(formattedGelar, " ") + " "
|
||||
}
|
||||
}
|
||||
|
||||
namaLengkap += strings.TrimSpace(dokter.NamaLengkap)
|
||||
|
||||
if dokter.GelarBelakang != "" {
|
||||
gelarBelakangList := strings.Split(dokter.GelarBelakang, "|")
|
||||
var formattedGelar []string
|
||||
|
||||
for _, gelar := range gelarBelakangList {
|
||||
gelar = strings.TrimSpace(gelar)
|
||||
if gelar != "" {
|
||||
formattedGelar = append(formattedGelar, gelar)
|
||||
}
|
||||
}
|
||||
|
||||
if len(formattedGelar) > 0 {
|
||||
namaLengkap += ", " + strings.Join(formattedGelar, ", ")
|
||||
}
|
||||
}
|
||||
|
||||
formatted := &modelsMaster.DokterFormatted{
|
||||
NamaLengkapFormatted: strings.TrimSpace(namaLengkap),
|
||||
Spesialis: strings.TrimSpace(dokter.Spesialis),
|
||||
Subspesialis: strings.TrimSpace(dokter.Subspesialis),
|
||||
}
|
||||
|
||||
result = append(result, formatted)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func (h *DokterHandler) GetDokterBySpesialisIDs(ctx context.Context, dbConn *sqlx.DB, spesialisIDs []int) ([]*modelsMaster.ListDokter, error) {
|
||||
query, args, err := sqlx.In(`
|
||||
SELECT
|
||||
ARRAY_TO_STRING(
|
||||
ARRAY(
|
||||
SELECT dgd."Gelar_depan"
|
||||
FROM data_pegawai_daftar_gelar_depan dpdgd
|
||||
LEFT JOIN daftar_gelar_depan dgd ON dpdgd.daftar_gelar_depan_id = dgd.id
|
||||
WHERE dpdgd.data_pegawai_id = dp.id
|
||||
), '|'
|
||||
) AS gelardepan,
|
||||
COALESCE(dk."Nama_lengkap", '') AS namalengkap,
|
||||
ARRAY_TO_STRING(
|
||||
ARRAY(
|
||||
SELECT dgb."Gelar_belakang"
|
||||
FROM data_pegawai_daftar_gelar_belakang dpdgb
|
||||
LEFT JOIN daftar_gelar_belakang dgb ON dpdgb.daftar_gelar_belakang_id = dgb.id
|
||||
WHERE dpdgb.data_pegawai_id = dp.id
|
||||
), '|'
|
||||
) AS gelarbelakang,
|
||||
ds."Subspesialis",
|
||||
dss."Spesialis"
|
||||
FROM "data_pegawai" dp
|
||||
LEFT JOIN data_ktp dk ON dp."KTP" = dk.id
|
||||
LEFT JOIN daftar_subspesialis ds ON dp."Subspesialis" = ds.id
|
||||
LEFT JOIN daftar_spesialis dss ON ds."FK_daftar_spesialis_ID" = dss.id
|
||||
WHERE dss.id IN (?)
|
||||
ORDER BY dk."Nama_lengkap" ASC
|
||||
`, spesialisIDs)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to build query: %w", err)
|
||||
}
|
||||
|
||||
query = dbConn.Rebind(query)
|
||||
|
||||
var dokterList []*modelsMaster.ListDokter
|
||||
err = dbConn.SelectContext(ctx, &dokterList, query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute query: %w", err)
|
||||
}
|
||||
|
||||
return dokterList, nil
|
||||
}
|
||||
@@ -0,0 +1,403 @@
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,949 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user