849 lines
25 KiB
Go
849 lines
25 KiB
Go
package handlers
|
|
|
|
import (
|
|
"api-service/internal/config"
|
|
"api-service/internal/database"
|
|
models "api-service/internal/models"
|
|
registrasi "api-service/internal/models/registrasi"
|
|
"api-service/internal/utils/validation"
|
|
"context"
|
|
"database/sql"
|
|
"fmt"
|
|
"log"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/go-playground/validator/v10"
|
|
)
|
|
|
|
var (
|
|
registrationCounterDB database.Service
|
|
registrationCounterOnce sync.Once
|
|
registrationCounterValidate *validator.Validate
|
|
)
|
|
|
|
// Initialize the database connection and validator
|
|
func init() {
|
|
registrationCounterOnce.Do(func() {
|
|
registrationCounterDB = database.New(config.LoadConfig())
|
|
registrationCounterValidate = validator.New()
|
|
// Jika ada validasi khusus untuk status, daftarkan di sini
|
|
// registrationCounterValidate.RegisterValidation("registration_counter_status", validateRegistrationCounterStatus)
|
|
if registrationCounterDB == nil {
|
|
log.Fatal("Failed to initialize database connection for registration counter")
|
|
}
|
|
})
|
|
}
|
|
|
|
// RegistrationCounterHandler handles registration counter services
|
|
type RegistrationCounterHandler struct {
|
|
db database.Service
|
|
}
|
|
|
|
// NewRegistrationCounterHandler creates a new RegistrationCounterHandler
|
|
func NewRegistrationCounterHandler() *RegistrationCounterHandler {
|
|
return &RegistrationCounterHandler{
|
|
db: registrationCounterDB,
|
|
}
|
|
}
|
|
|
|
// GetRegistrationCounters godoc
|
|
// @Summary Get registration counters with pagination and optional aggregation
|
|
// @Description Returns a paginated list of registration counters with optional summary statistics
|
|
// @Tags RegistrationCounter
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param limit query int false "Limit (max 100)" default(10)
|
|
// @Param offset query int false "Offset" default(0)
|
|
// @Param include_summary query bool false "Include aggregation summary" default(false)
|
|
// @Param active query bool false "Filter by active status"
|
|
// @Param search query string false "Search in name or code"
|
|
// @Success 200 {object} registrasi.MsRegistrationCounterGetResponse "Success response"
|
|
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router /registration-counter [get]
|
|
func (h *RegistrationCounterHandler) GetRegistrationCounters(c *gin.Context) {
|
|
// Parse pagination parameters
|
|
limit, offset, err := h.parsePaginationParams(c)
|
|
if err != nil {
|
|
h.respondError(c, "Invalid pagination parameters", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Parse filter parameters
|
|
filter := h.parseFilterParams(c)
|
|
includeAggregation := c.Query("include_summary") == "true"
|
|
|
|
// Get database connection
|
|
dbConn, err := h.db.GetDB("default")
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
// Create context with timeout
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
|
|
defer cancel()
|
|
|
|
// Execute concurrent operations
|
|
var (
|
|
items []registrasi.MsRegistrationCounter
|
|
total int
|
|
aggregateData *models.AggregateData
|
|
wg sync.WaitGroup
|
|
errChan = make(chan error, 3)
|
|
mu sync.Mutex
|
|
)
|
|
|
|
// Fetch total count
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
if err := h.getTotalCount(ctx, dbConn, filter, &total); err != nil {
|
|
mu.Lock()
|
|
errChan <- fmt.Errorf("failed to get total count: %w", err)
|
|
mu.Unlock()
|
|
}
|
|
}()
|
|
|
|
// Fetch main data
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
result, err := h.fetchRegistrationCounters(ctx, dbConn, filter, limit, offset)
|
|
mu.Lock()
|
|
if err != nil {
|
|
errChan <- fmt.Errorf("failed to fetch data: %w", err)
|
|
} else {
|
|
items = result
|
|
}
|
|
mu.Unlock()
|
|
}()
|
|
|
|
// Fetch aggregation data if requested
|
|
if includeAggregation {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
result, err := h.getAggregateData(ctx, dbConn, filter)
|
|
mu.Lock()
|
|
if err != nil {
|
|
errChan <- fmt.Errorf("failed to get aggregate data: %w", err)
|
|
} else {
|
|
aggregateData = result
|
|
}
|
|
mu.Unlock()
|
|
}()
|
|
}
|
|
|
|
// Wait for all goroutines
|
|
wg.Wait()
|
|
close(errChan)
|
|
|
|
// Check for errors
|
|
for err := range errChan {
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Data processing failed", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
// Build response
|
|
meta := h.calculateMeta(limit, offset, total)
|
|
response := registrasi.MsRegistrationCounterGetResponse{
|
|
Message: "Data registration counter berhasil diambil",
|
|
Data: items,
|
|
Meta: meta,
|
|
}
|
|
|
|
if includeAggregation && aggregateData != nil {
|
|
response.Summary = aggregateData
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// GetRegistrationCounterByID godoc
|
|
// @Summary Get Registration Counter by ID
|
|
// @Description Returns a single registration counter by its ID
|
|
// @Tags RegistrationCounter
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path int true "Registration Counter ID"
|
|
// @Success 200 {object} registrasi.MsRegistrationCounterGetByIDResponse "Success response"
|
|
// @Failure 400 {object} models.ErrorResponse "Invalid ID format"
|
|
// @Failure 404 {object} models.ErrorResponse "Registration Counter not found"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router /registration-counter/{id} [get]
|
|
func (h *RegistrationCounterHandler) GetRegistrationCounterByID(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
|
if err != nil {
|
|
h.respondError(c, "Invalid ID format", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
dbConn, err := h.db.GetDB("default")
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
item, err := h.getRegistrationCounterByID(ctx, dbConn, id)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
h.respondError(c, "Registration Counter not found", err, http.StatusNotFound)
|
|
} else {
|
|
h.logAndRespondError(c, "Failed to get registration counter", err, http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
|
|
response := registrasi.MsRegistrationCounterGetByIDResponse{
|
|
Message: "Detail registration counter berhasil diambil",
|
|
Data: item,
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// CreateRegistrationCounter godoc
|
|
// @Summary Create a new registration counter
|
|
// @Description Creates a new registration counter record
|
|
// @Tags RegistrationCounter
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param request body registrasi.MsRegistrationCounterCreateRequest true "Registration Counter creation request"
|
|
// @Success 201 {object} registrasi.MsRegistrationCounterCreateResponse "Registration Counter created successfully"
|
|
// @Failure 400 {object} models.ErrorResponse "Bad request or validation error"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router /registration-counter [post]
|
|
func (h *RegistrationCounterHandler) CreateRegistrationCounter(c *gin.Context) {
|
|
var req registrasi.MsRegistrationCounterCreateRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
h.respondError(c, "Invalid request body", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Validate request
|
|
if err := registrationCounterValidate.Struct(&req); err != nil {
|
|
h.respondError(c, "Validation failed", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
dbConn, err := h.db.GetDB("default")
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
// Validate duplicate entries
|
|
if err := h.validateRegistrationCounterSubmission(ctx, dbConn, &req); err != nil {
|
|
h.respondError(c, "Validation failed", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
item, err := h.createRegistrationCounter(ctx, dbConn, &req)
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Failed to create registration counter", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
response := registrasi.MsRegistrationCounterCreateResponse{
|
|
Message: "Registration counter berhasil dibuat",
|
|
Data: item,
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, response)
|
|
}
|
|
|
|
// UpdateRegistrationCounter godoc
|
|
// @Summary Update an existing registration counter
|
|
// @Description Updates an existing registration counter record
|
|
// @Tags RegistrationCounter
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path int true "Registration Counter ID"
|
|
// @Param request body registrasi.MsRegistrationCounterUpdateRequest true "Registration Counter update request"
|
|
// @Success 200 {object} registrasi.MsRegistrationCounterUpdateResponse "Registration Counter updated successfully"
|
|
// @Failure 400 {object} models.ErrorResponse "Bad request or validation error"
|
|
// @Failure 404 {object} models.ErrorResponse "Registration Counter not found"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router /registration-counter/{id} [put]
|
|
func (h *RegistrationCounterHandler) UpdateRegistrationCounter(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
// Validate ID is integer
|
|
if _, err := strconv.Atoi(id); err != nil {
|
|
h.respondError(c, "Invalid ID format", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
var req registrasi.MsRegistrationCounterUpdateRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
h.respondError(c, "Invalid request body", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
// Set ID from path parameter
|
|
idInt, _ := strconv.Atoi(id)
|
|
req.ID = idInt
|
|
|
|
// Validate request
|
|
if err := registrationCounterValidate.Struct(&req); err != nil {
|
|
h.respondError(c, "Validation failed", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
dbConn, err := h.db.GetDB("default")
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
item, err := h.updateRegistrationCounter(ctx, dbConn, &req)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
h.respondError(c, "Registration Counter not found", err, http.StatusNotFound)
|
|
} else {
|
|
h.logAndRespondError(c, "Failed to update registration counter", err, http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
|
|
response := registrasi.MsRegistrationCounterUpdateResponse{
|
|
Message: "Registration counter berhasil diperbarui",
|
|
Data: item,
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// DeleteRegistrationCounter godoc
|
|
// @Summary Soft delete a registration counter
|
|
// @Description Soft deletes a registration counter by setting active to false
|
|
// @Tags RegistrationCounter
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param id path int true "Registration Counter ID"
|
|
// @Success 200 {object} registrasi.MsRegistrationCounterDeleteResponse "Registration Counter deleted successfully"
|
|
// @Failure 400 {object} models.ErrorResponse "Invalid ID format"
|
|
// @Failure 404 {object} models.ErrorResponse "Registration Counter not found"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router /registration-counter/{id} [delete]
|
|
func (h *RegistrationCounterHandler) DeleteRegistrationCounter(c *gin.Context) {
|
|
idStr := c.Param("id")
|
|
id, err := strconv.ParseInt(idStr, 10, 64)
|
|
if err != nil {
|
|
h.respondError(c, "Invalid ID format", err, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
dbConn, err := h.db.GetDB("default")
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
err = h.deleteRegistrationCounter(ctx, dbConn, id)
|
|
if err != nil {
|
|
if err == sql.ErrNoRows {
|
|
h.respondError(c, "Registration Counter not found", err, http.StatusNotFound)
|
|
} else {
|
|
h.logAndRespondError(c, "Failed to delete registration counter", err, http.StatusInternalServerError)
|
|
}
|
|
return
|
|
}
|
|
|
|
response := registrasi.MsRegistrationCounterDeleteResponse{
|
|
Message: "Registration counter berhasil dihapus (diset tidak aktif)",
|
|
ID: idStr,
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
// GetRegistrationCounterStats godoc
|
|
// @Summary Get registration counter statistics
|
|
// @Description Returns comprehensive statistics about registration counter data
|
|
// @Tags RegistrationCounter
|
|
// @Accept json
|
|
// @Produce json
|
|
// @Param active query bool false "Filter statistics by active status"
|
|
// @Success 200 {object} models.AggregateData "Statistics data"
|
|
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
|
// @Router /registration-counter/stats [get]
|
|
func (h *RegistrationCounterHandler) GetRegistrationCounterStats(c *gin.Context) {
|
|
dbConn, err := h.db.GetDB("default")
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Database connection failed", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
filter := h.parseFilterParams(c)
|
|
aggregateData, err := h.getAggregateData(ctx, dbConn, filter)
|
|
if err != nil {
|
|
h.logAndRespondError(c, "Failed to get statistics", err, http.StatusInternalServerError)
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"message": "Statistik registration counter berhasil diambil",
|
|
"data": aggregateData,
|
|
})
|
|
}
|
|
|
|
// Database operations
|
|
// func (h *RegistrationCounterHandler) getRegistrationCounterByID(ctx context.Context, dbConn *sql.DB, id int64) (*registrasi.MsRegistrationCounter, error) {
|
|
// query := `SELECT id, name, code, icon, quota, active, fk_ref_healtcare_type_id, fk_ref_service_type_id, fk_sd_location_id, ds_sd_location
|
|
// FROM master.ms_registration_counter WHERE id = $1`
|
|
// row := dbConn.QueryRowContext(ctx, query, id)
|
|
|
|
// var item registrasi.MsRegistrationCounter
|
|
// err := row.Scan(
|
|
// &item.ID,
|
|
// &item.Name,
|
|
// &item.Code,
|
|
// &item.Icon,
|
|
// &item.Quota,
|
|
// &item.Active,
|
|
// &item.FKRefHealtcareTypeID,
|
|
// &item.FKRefServiceTypeID,
|
|
// &item.FKSdLocationID,
|
|
// &item.DsSdLocation,
|
|
// )
|
|
// if err != nil {
|
|
// return nil, err
|
|
// }
|
|
|
|
// return &item, nil
|
|
// }
|
|
|
|
func (h *RegistrationCounterHandler) getRegistrationCounterByID(ctx context.Context, dbConn *sql.DB, id int64) (*registrasi.MsRegistrationCounterDetail, error) {
|
|
query := `
|
|
SELECT rc.id, rc.name, rc.code, rc.icon, rc.quota, rc.active,
|
|
ht.name as healthcaretype,
|
|
st.name as servicetype
|
|
FROM master.ms_registration_counter rc
|
|
LEFT JOIN reference.ref_healthcare_type ht ON rc.fk_ref_healtcare_type_id = ht.id
|
|
LEFT JOIN reference.ref_service_type st ON rc.fk_ref_service_type_id = st.id
|
|
WHERE rc.id = $1`
|
|
row := dbConn.QueryRowContext(ctx, query, id)
|
|
|
|
var item registrasi.MsRegistrationCounterDetail
|
|
err := row.Scan(
|
|
&item.ID,
|
|
&item.Name,
|
|
&item.Code,
|
|
&item.Icon,
|
|
&item.Quota,
|
|
&item.Active,
|
|
&item.HealthCareType,
|
|
&item.ServiceType,
|
|
)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &item, nil
|
|
}
|
|
|
|
func (h *RegistrationCounterHandler) createRegistrationCounter(ctx context.Context, dbConn *sql.DB, req *registrasi.MsRegistrationCounterCreateRequest) (*registrasi.MsRegistrationCounter, error) {
|
|
query := `INSERT INTO master.ms_registration_counter
|
|
(name, code, icon, quota, active, fk_ref_healtcare_type_id, fk_ref_service_type_id, fk_sd_location_id, ds_sd_location)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
|
RETURNING id, name, code, icon, quota, active, fk_ref_healtcare_type_id, fk_ref_service_type_id, fk_sd_location_id, ds_sd_location`
|
|
|
|
row := dbConn.QueryRowContext(
|
|
ctx,
|
|
query,
|
|
req.Name,
|
|
req.Code,
|
|
req.Icon,
|
|
req.Quota,
|
|
req.Active,
|
|
req.FKRefHealtcareTypeID,
|
|
req.FKRefServiceTypeID,
|
|
req.FKSdLocationID,
|
|
req.DsSdLocation,
|
|
)
|
|
|
|
var item registrasi.MsRegistrationCounter
|
|
err := row.Scan(
|
|
&item.ID,
|
|
&item.Name,
|
|
&item.Code,
|
|
&item.Icon,
|
|
&item.Quota,
|
|
&item.Active,
|
|
&item.FKRefHealtcareTypeID,
|
|
&item.FKRefServiceTypeID,
|
|
&item.FKSdLocationID,
|
|
&item.DsSdLocation,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create registration counter: %w", err)
|
|
}
|
|
|
|
return &item, nil
|
|
}
|
|
|
|
func (h *RegistrationCounterHandler) updateRegistrationCounter(ctx context.Context, dbConn *sql.DB, req *registrasi.MsRegistrationCounterUpdateRequest) (*registrasi.MsRegistrationCounter, error) {
|
|
query := `UPDATE master.ms_registration_counter
|
|
SET name = $2, code = $3, icon = $4, quota = $5, active = $6, fk_ref_healtcare_type_id = $7, fk_ref_service_type_id = $8, fk_sd_location_id = $9, ds_sd_location = $10
|
|
WHERE id = $1
|
|
RETURNING id, name, code, icon, quota, active, fk_ref_healtcare_type_id, fk_ref_service_type_id, fk_sd_location_id, ds_sd_location`
|
|
|
|
row := dbConn.QueryRowContext(
|
|
ctx,
|
|
query,
|
|
req.ID,
|
|
req.Name,
|
|
req.Code,
|
|
req.Icon,
|
|
req.Quota,
|
|
req.Active,
|
|
req.FKRefHealtcareTypeID,
|
|
req.FKRefServiceTypeID,
|
|
req.FKSdLocationID,
|
|
req.DsSdLocation,
|
|
)
|
|
|
|
var item registrasi.MsRegistrationCounter
|
|
err := row.Scan(
|
|
&item.ID,
|
|
&item.Name,
|
|
&item.Code,
|
|
&item.Icon,
|
|
&item.Quota,
|
|
&item.Active,
|
|
&item.FKRefHealtcareTypeID,
|
|
&item.FKRefServiceTypeID,
|
|
&item.FKSdLocationID,
|
|
&item.DsSdLocation,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to update registration counter: %w", err)
|
|
}
|
|
|
|
return &item, nil
|
|
}
|
|
|
|
func (h *RegistrationCounterHandler) deleteRegistrationCounter(ctx context.Context, dbConn *sql.DB, id int64) error {
|
|
query := `UPDATE master.ms_registration_counter SET active = false WHERE id = $1`
|
|
result, err := dbConn.ExecContext(ctx, query, id)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to delete registration counter: %w", err)
|
|
}
|
|
|
|
rowsAffected, err := result.RowsAffected()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to get affected rows: %w", err)
|
|
}
|
|
|
|
if rowsAffected == 0 {
|
|
return sql.ErrNoRows
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func (h *RegistrationCounterHandler) fetchRegistrationCounters(ctx context.Context, dbConn *sql.DB, filter registrasi.MsRegistrationCounterFilter, limit, offset int) ([]registrasi.MsRegistrationCounter, error) {
|
|
whereClause, args := h.buildWhereClause(filter)
|
|
query := fmt.Sprintf(`SELECT id, name, code, icon, quota, active, fk_ref_healtcare_type_id, fk_ref_service_type_id, fk_sd_location_id, ds_sd_location
|
|
FROM master.ms_registration_counter
|
|
WHERE %s ORDER BY id LIMIT $%d OFFSET $%d`, whereClause, len(args)+1, len(args)+2)
|
|
args = append(args, limit, offset)
|
|
|
|
rows, err := dbConn.QueryContext(ctx, query, args...)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("fetch registration counters query failed: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
items := make([]registrasi.MsRegistrationCounter, 0, limit)
|
|
for rows.Next() {
|
|
var item registrasi.MsRegistrationCounter
|
|
err := rows.Scan(
|
|
&item.ID,
|
|
&item.Name,
|
|
&item.Code,
|
|
&item.Icon,
|
|
&item.Quota,
|
|
&item.Active,
|
|
&item.FKRefHealtcareTypeID,
|
|
&item.FKRefServiceTypeID,
|
|
&item.FKSdLocationID,
|
|
&item.DsSdLocation,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("scan registration counter failed: %w", err)
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("rows iteration error: %w", err)
|
|
}
|
|
|
|
log.Printf("Successfully fetched %d registration counters with filters applied", len(items))
|
|
return items, nil
|
|
}
|
|
|
|
func (h *RegistrationCounterHandler) getTotalCount(ctx context.Context, dbConn *sql.DB, filter registrasi.MsRegistrationCounterFilter, total *int) error {
|
|
whereClause, args := h.buildWhereClause(filter)
|
|
countQuery := fmt.Sprintf("SELECT COUNT(*) FROM master.ms_registration_counter WHERE %s", whereClause)
|
|
if err := dbConn.QueryRowContext(ctx, countQuery, args...).Scan(total); err != nil {
|
|
return fmt.Errorf("total count query failed: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (h *RegistrationCounterHandler) getAggregateData(ctx context.Context, dbConn *sql.DB, filter registrasi.MsRegistrationCounterFilter) (*models.AggregateData, error) {
|
|
aggregate := &models.AggregateData{
|
|
ByStatus: make(map[string]int),
|
|
}
|
|
|
|
whereClause, args := h.buildWhereClause(filter)
|
|
|
|
var wg sync.WaitGroup
|
|
var mu sync.Mutex
|
|
errChan := make(chan error, 2)
|
|
|
|
// 1. Count by active status
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
statusQuery := fmt.Sprintf("SELECT active, COUNT(*) FROM master.ms_registration_counter WHERE %s GROUP BY active ORDER BY active", whereClause)
|
|
rows, err := dbConn.QueryContext(ctx, statusQuery, args...)
|
|
if err != nil {
|
|
errChan <- fmt.Errorf("status query failed: %w", err)
|
|
return
|
|
}
|
|
defer rows.Close()
|
|
|
|
mu.Lock()
|
|
for rows.Next() {
|
|
var active bool
|
|
var count int
|
|
if err := rows.Scan(&active, &count); err != nil {
|
|
mu.Unlock()
|
|
errChan <- fmt.Errorf("status scan failed: %w", err)
|
|
return
|
|
}
|
|
if active {
|
|
aggregate.ByStatus["active"] = count
|
|
aggregate.TotalActive = count
|
|
} else {
|
|
aggregate.ByStatus["inactive"] = count
|
|
aggregate.TotalInactive = count
|
|
}
|
|
}
|
|
mu.Unlock()
|
|
|
|
if err := rows.Err(); err != nil {
|
|
errChan <- fmt.Errorf("status iteration error: %w", err)
|
|
}
|
|
}()
|
|
|
|
// 2. Get total count
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
|
|
// Last updated
|
|
lastUpdatedQuery := fmt.Sprintf("SELECT MAX(date_updated) FROM master.ms_registration_counter WHERE %s AND date_updated IS NOT NULL", whereClause)
|
|
var lastUpdated sql.NullTime
|
|
if err := dbConn.QueryRowContext(ctx, lastUpdatedQuery, args...).Scan(&lastUpdated); err != nil {
|
|
errChan <- fmt.Errorf("last updated query failed: %w", err)
|
|
return
|
|
}
|
|
|
|
// Today statistics
|
|
today := time.Now().Format("2006-01-02")
|
|
todayStatsQuery := fmt.Sprintf(`
|
|
SELECT
|
|
SUM(CASE WHEN DATE(date_created) = $%d THEN 1 ELSE 0 END) as created_today,
|
|
SUM(CASE WHEN DATE(date_updated) = $%d AND DATE(date_created) != $%d THEN 1 ELSE 0 END) as updated_today
|
|
FROM master.ms_registration_counter
|
|
WHERE %s`, len(args)+1, len(args)+1, len(args)+1, whereClause)
|
|
|
|
todayArgs := append(args, today)
|
|
var createdToday, updatedToday int
|
|
if err := dbConn.QueryRowContext(ctx, todayStatsQuery, todayArgs...).Scan(&createdToday, &updatedToday); err != nil {
|
|
errChan <- fmt.Errorf("today stats query failed: %w", err)
|
|
return
|
|
}
|
|
|
|
mu.Lock()
|
|
if lastUpdated.Valid {
|
|
aggregate.LastUpdated = &lastUpdated.Time
|
|
}
|
|
aggregate.CreatedToday = createdToday
|
|
aggregate.UpdatedToday = updatedToday
|
|
mu.Unlock()
|
|
}()
|
|
|
|
// Wait for all goroutines
|
|
wg.Wait()
|
|
close(errChan)
|
|
|
|
// Check for errors
|
|
for err := range errChan {
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return aggregate, nil
|
|
}
|
|
|
|
// Helper methods
|
|
func (h *RegistrationCounterHandler) logAndRespondError(c *gin.Context, message string, err error, statusCode int) {
|
|
log.Printf("[ERROR] %s: %v", message, err)
|
|
h.respondError(c, message, err, statusCode)
|
|
}
|
|
|
|
func (h *RegistrationCounterHandler) respondError(c *gin.Context, message string, err error, statusCode int) {
|
|
errorMessage := message
|
|
if gin.Mode() == gin.ReleaseMode {
|
|
errorMessage = "Internal server error"
|
|
}
|
|
|
|
c.JSON(statusCode, models.ErrorResponse{
|
|
Error: errorMessage,
|
|
Code: statusCode,
|
|
Message: err.Error(),
|
|
Timestamp: time.Now(),
|
|
})
|
|
}
|
|
|
|
func (h *RegistrationCounterHandler) parsePaginationParams(c *gin.Context) (int, int, error) {
|
|
limit := 10
|
|
offset := 0
|
|
|
|
if limitStr := c.Query("limit"); limitStr != "" {
|
|
parsedLimit, err := strconv.Atoi(limitStr)
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("invalid limit parameter: %s", limitStr)
|
|
}
|
|
if parsedLimit <= 0 || parsedLimit > 100 {
|
|
return 0, 0, fmt.Errorf("limit must be between 1 and 100")
|
|
}
|
|
limit = parsedLimit
|
|
}
|
|
|
|
if offsetStr := c.Query("offset"); offsetStr != "" {
|
|
parsedOffset, err := strconv.Atoi(offsetStr)
|
|
if err != nil {
|
|
return 0, 0, fmt.Errorf("invalid offset parameter: %s", offsetStr)
|
|
}
|
|
if parsedOffset < 0 {
|
|
return 0, 0, fmt.Errorf("offset cannot be negative")
|
|
}
|
|
offset = parsedOffset
|
|
}
|
|
|
|
return limit, offset, nil
|
|
}
|
|
|
|
func (h *RegistrationCounterHandler) parseFilterParams(c *gin.Context) registrasi.MsRegistrationCounterFilter {
|
|
filter := registrasi.MsRegistrationCounterFilter{}
|
|
|
|
if status := c.Query("active"); status != "" {
|
|
if models.IsValidStatus(status) {
|
|
filter.Status = &status
|
|
}
|
|
}
|
|
|
|
if search := c.Query("search"); search != "" {
|
|
filter.Search = &search
|
|
}
|
|
|
|
return filter
|
|
}
|
|
|
|
func (h *RegistrationCounterHandler) buildWhereClause(filter registrasi.MsRegistrationCounterFilter) (string, []interface{}) {
|
|
conditions := []string{"1=1"}
|
|
args := []interface{}{}
|
|
paramCount := 1
|
|
|
|
if filter.Status != nil {
|
|
conditions = append(conditions, fmt.Sprintf("active = $%d", paramCount))
|
|
args = append(args, *filter.Status)
|
|
paramCount++
|
|
}
|
|
|
|
if filter.Search != nil {
|
|
searchCondition := fmt.Sprintf("(name ILIKE $%d OR code ILIKE $%d)", paramCount, paramCount)
|
|
conditions = append(conditions, searchCondition)
|
|
searchTerm := "%" + *filter.Search + "%"
|
|
args = append(args, searchTerm)
|
|
paramCount++
|
|
}
|
|
|
|
return strings.Join(conditions, " AND "), args
|
|
}
|
|
|
|
func (h *RegistrationCounterHandler) calculateMeta(limit, offset, total int) models.MetaResponse {
|
|
totalPages := 0
|
|
currentPage := 1
|
|
if limit > 0 {
|
|
totalPages = (total + limit - 1) / limit
|
|
currentPage = (offset / limit) + 1
|
|
}
|
|
|
|
return models.MetaResponse{
|
|
Limit: limit,
|
|
Offset: offset,
|
|
Total: total,
|
|
TotalPages: totalPages,
|
|
CurrentPage: currentPage,
|
|
HasNext: offset+limit < total,
|
|
HasPrev: offset > 0,
|
|
}
|
|
}
|
|
|
|
func (h *RegistrationCounterHandler) validateRegistrationCounterSubmission(ctx context.Context, dbConn *sql.DB, req *registrasi.MsRegistrationCounterCreateRequest) error {
|
|
validator := validation.NewDuplicateValidator(dbConn)
|
|
|
|
config := validation.ValidationConfig{
|
|
TableName: "master.ms_registration_counter",
|
|
IDColumn: "id",
|
|
AdditionalFields: map[string]interface{}{
|
|
"name": req.Name,
|
|
"code": req.Code,
|
|
},
|
|
}
|
|
|
|
fields := map[string]interface{}{
|
|
"name": req.Name,
|
|
"code": req.Code,
|
|
}
|
|
|
|
err := validator.ValidateDuplicateWithCustomFields(ctx, config, fields)
|
|
if err != nil {
|
|
return fmt.Errorf("validation failed: %w", err)
|
|
}
|
|
|
|
return nil
|
|
} |