pembuatan service crud visit register pasien
Some checks failed
Go-test / build (push) Has been cancelled
Some checks failed
Go-test / build (push) Has been cancelled
This commit is contained in:
649
internal/handlers/visit/patientvisith.go
Normal file
649
internal/handlers/visit/patientvisith.go
Normal file
@@ -0,0 +1,649 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"api-service/internal/config"
|
||||
"api-service/internal/database"
|
||||
models "api-service/internal/models"
|
||||
pasienVisit "api-service/internal/models/visit"
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
var (
|
||||
patientVisitDB database.Service
|
||||
patientVisitOnce sync.Once
|
||||
patientVisitValidate *validator.Validate
|
||||
)
|
||||
|
||||
// Initialize the database connection and validator
|
||||
func init() {
|
||||
patientVisitOnce.Do(func() {
|
||||
patientVisitDB = database.New(config.LoadConfig())
|
||||
patientVisitValidate = validator.New()
|
||||
if patientVisitDB == nil {
|
||||
log.Fatal("Failed to initialize database connection for patient visit")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// PatientVisitHandler handles patient visit services
|
||||
type PatientVisitHandler struct {
|
||||
db database.Service
|
||||
}
|
||||
|
||||
// NewPatientVisitHandler creates a new PatientVisitHandler
|
||||
func NewPatientVisitHandler() *PatientVisitHandler {
|
||||
return &PatientVisitHandler{
|
||||
db: patientVisitDB,
|
||||
}
|
||||
}
|
||||
|
||||
// GetPatientVisits godoc
|
||||
// @Summary Get patient visits with pagination
|
||||
// @Description Returns a paginated list of patient visits
|
||||
// @Tags PatientVisit
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param limit query int false "Limit (max 100)" default(10)
|
||||
// @Param offset query int false "Offset" default(0)
|
||||
// @Param active query bool false "Filter by active status"
|
||||
// @Param check_in query bool false "Filter by check-in status"
|
||||
// @Param fk_ms_doctor_id query int false "Filter by doctor ID"
|
||||
// @Success 200 {object} models.TrPatientVisitGetListResponse "Success response"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /patient-visit [get]
|
||||
func (h *PatientVisitHandler) GetPatientVisits(c *gin.Context) {
|
||||
limit, offset, err := h.parsePaginationParams(c)
|
||||
if err != nil {
|
||||
h.respondError(c, "Invalid pagination parameters", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
filter := h.parseFilterParams(c)
|
||||
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(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
var (
|
||||
items []pasienVisit.TrPatientVisit
|
||||
total int
|
||||
wg sync.WaitGroup
|
||||
mu sync.Mutex
|
||||
)
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
if err := h.getTotalCount(ctx, dbConn, filter, &total); err != nil {
|
||||
mu.Lock()
|
||||
log.Printf("failed to get total count: %v", err)
|
||||
mu.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
result, err := h.fetchPatientVisits(ctx, dbConn, filter, limit, offset)
|
||||
mu.Lock()
|
||||
if err != nil {
|
||||
log.Printf("failed to fetch data: %v", err)
|
||||
} else {
|
||||
items = result
|
||||
}
|
||||
mu.Unlock()
|
||||
}()
|
||||
|
||||
wg.Wait()
|
||||
|
||||
meta := h.calculateMeta(limit, offset, total)
|
||||
response := pasienVisit.TrPatientVisitGetListResponse{
|
||||
Message: "Data patient visit berhasil diambil",
|
||||
Data: items,
|
||||
Meta: meta,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// CreatePatientVisit godoc
|
||||
// @Summary Create a new patient visit
|
||||
// @Description Creates a new patient visit record
|
||||
// @Tags PatientVisit
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body models.TrPatientVisitCreateRequest true "Patient Visit creation request"
|
||||
// @Success 201 {object} models.TrPatientVisitCreateResponse "Patient Visit created successfully"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request or validation error"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /patient-visit [post]
|
||||
func (h *PatientVisitHandler) CreatePatientVisit(c *gin.Context) {
|
||||
var req pasienVisit.TrPatientVisitCreateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.respondError(c, "Invalid request body", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if err := patientVisitValidate.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.createPatientVisit(ctx, dbConn, &req)
|
||||
if err != nil {
|
||||
h.logAndRespondError(c, "Failed to create patient visit", err, http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
response := pasienVisit.TrPatientVisitCreateResponse{
|
||||
Message: "Patient visit berhasil dibuat",
|
||||
Data: item,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, response)
|
||||
}
|
||||
|
||||
// UpdatePatientVisit godoc
|
||||
// @Summary Update an existing patient visit (full update)
|
||||
// @Description Updates an existing patient visit record with all fields
|
||||
// @Tags PatientVisit
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "Patient Visit ID"
|
||||
// @Param request body models.TrPatientVisitUpdateRequest true "Patient Visit update request"
|
||||
// @Success 200 {object} models.TrPatientVisitUpdateResponse "Patient Visit updated successfully"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request or validation error"
|
||||
// @Failure 404 {object} models.ErrorResponse "Patient Visit not found"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /patient-visit/{id} [put]
|
||||
func (h *PatientVisitHandler) UpdatePatientVisit(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseInt(idStr, 10, 32)
|
||||
if err != nil {
|
||||
h.respondError(c, "Invalid ID format", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
var req pasienVisit.TrPatientVisitUpdateRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
h.respondError(c, "Invalid request body", err, http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
req.ID = int32(id)
|
||||
if err := patientVisitValidate.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.updatePatientVisit(ctx, dbConn, &req)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
h.respondError(c, "Patient Visit not found", err, http.StatusNotFound)
|
||||
} else {
|
||||
h.logAndRespondError(c, "Failed to update patient visit", err, http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response := pasienVisit.TrPatientVisitUpdateResponse{
|
||||
Message: "Patient visit berhasil diperbarui",
|
||||
Data: item,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
// PatchPatientVisit godoc
|
||||
// @Summary Partially update an existing patient visit
|
||||
// @Description Partially updates an existing patient visit record. Only send the fields you want to change.
|
||||
// @Tags PatientVisit
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path int true "Patient Visit ID"
|
||||
// @Param request body models.TrPatientVisitPatchRequest true "Patient Visit patch request"
|
||||
// @Success 200 {object} models.TrPatientVisitPatchResponse "Patient Visit patched successfully"
|
||||
// @Failure 400 {object} models.ErrorResponse "Bad request or validation error"
|
||||
// @Failure 404 {object} models.ErrorResponse "Patient Visit not found"
|
||||
// @Failure 500 {object} models.ErrorResponse "Internal server error"
|
||||
// @Router /patient-visit/{id} [patch]
|
||||
func (h *PatientVisitHandler) PatchPatientVisit(c *gin.Context) {
|
||||
idStr := c.Param("id")
|
||||
id, err := strconv.ParseInt(idStr, 10, 32)
|
||||
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.cancelPatientVisit(ctx, dbConn, int32(id))
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
h.respondError(c, "Patient Visit not found", err, http.StatusNotFound)
|
||||
} else if err.Error() == "already canceled" {
|
||||
h.respondError(c, "Patient visit is already canceled", err, http.StatusBadRequest)
|
||||
} else {
|
||||
h.logAndRespondError(c, "Failed to cancel patient visit", err, http.StatusInternalServerError)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
response := pasienVisit.TrPatientVisitDeleteResponse{
|
||||
Message: "Patient visit berhasil dibatalkan",
|
||||
ID: idStr,
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, response)
|
||||
}
|
||||
|
||||
|
||||
func (h *PatientVisitHandler) cancelPatientVisit(ctx context.Context, dbConn *sql.DB, id int32) error {
|
||||
|
||||
var currentActiveStatus sql.NullBool
|
||||
checkQuery := `SELECT active FROM "transaction".tr_patient_visit WHERE id = $1`
|
||||
err := dbConn.QueryRowContext(ctx, checkQuery, id).Scan(¤tActiveStatus)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
return fmt.Errorf("failed to check patient visit status: %w", err)
|
||||
}
|
||||
|
||||
if currentActiveStatus.Valid && !currentActiveStatus.Bool {
|
||||
return fmt.Errorf("already canceled")
|
||||
}
|
||||
|
||||
query := `UPDATE "transaction".tr_patient_visit SET active = false WHERE id = $1`
|
||||
result, err := dbConn.ExecContext(ctx, query, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to cancel patient visit: %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 *PatientVisitHandler) getPatientVisitByID(ctx context.Context, dbConn *sql.DB, id int32) (*pasienVisit.TrPatientVisit, error) {
|
||||
query := `SELECT id, barcode, registration_date, service_date, check_in_date, check_in, active, fk_ms_doctor_id
|
||||
FROM "transaction".tr_patient_visit WHERE id = $1`
|
||||
row := dbConn.QueryRowContext(ctx, query, id)
|
||||
|
||||
var item pasienVisit.TrPatientVisit
|
||||
err := row.Scan(
|
||||
&item.ID,
|
||||
&item.Barcode,
|
||||
&item.RegistrationDate,
|
||||
&item.ServiceDate,
|
||||
&item.CheckInDate,
|
||||
&item.CheckIn,
|
||||
&item.Active,
|
||||
&item.FKMsDoctorID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (h *PatientVisitHandler) createPatientVisit(ctx context.Context, dbConn *sql.DB, req *pasienVisit.TrPatientVisitCreateRequest) (*pasienVisit.TrPatientVisit, error) {
|
||||
query := `INSERT INTO "transaction".tr_patient_visit
|
||||
(barcode, registration_date, service_date, check_in_date, check_in, active, fk_ms_doctor_id)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id, barcode, registration_date, service_date, check_in_date, check_in, active, fk_ms_doctor_id`
|
||||
|
||||
row := dbConn.QueryRowContext(
|
||||
ctx,
|
||||
query,
|
||||
req.Barcode,
|
||||
req.RegistrationDate,
|
||||
req.ServiceDate,
|
||||
req.CheckInDate,
|
||||
req.CheckIn,
|
||||
req.Active,
|
||||
req.FKMsDoctorID,
|
||||
)
|
||||
|
||||
var item pasienVisit.TrPatientVisit
|
||||
err := row.Scan(
|
||||
&item.ID,
|
||||
&item.Barcode,
|
||||
&item.RegistrationDate,
|
||||
&item.ServiceDate,
|
||||
&item.CheckInDate,
|
||||
&item.CheckIn,
|
||||
&item.Active,
|
||||
&item.FKMsDoctorID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create patient visit: %w", err)
|
||||
}
|
||||
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (h *PatientVisitHandler) updatePatientVisit(ctx context.Context, dbConn *sql.DB, req *pasienVisit.TrPatientVisitUpdateRequest) (*pasienVisit.TrPatientVisit, error) {
|
||||
query := `UPDATE "transaction".tr_patient_visit
|
||||
SET barcode = $2, registration_date = $3, service_date = $4, check_in_date = $5, check_in = $6, active = $7, fk_ms_doctor_id = $8
|
||||
WHERE id = $1
|
||||
RETURNING id, barcode, registration_date, service_date, check_in_date, check_in, active, fk_ms_doctor_id`
|
||||
|
||||
row := dbConn.QueryRowContext(
|
||||
ctx,
|
||||
query,
|
||||
req.ID,
|
||||
req.Barcode,
|
||||
req.RegistrationDate,
|
||||
req.ServiceDate,
|
||||
req.CheckInDate,
|
||||
req.CheckIn,
|
||||
req.Active,
|
||||
req.FKMsDoctorID,
|
||||
)
|
||||
|
||||
var item pasienVisit.TrPatientVisit
|
||||
err := row.Scan(
|
||||
&item.ID,
|
||||
&item.Barcode,
|
||||
&item.RegistrationDate,
|
||||
&item.ServiceDate,
|
||||
&item.CheckInDate,
|
||||
&item.CheckIn,
|
||||
&item.Active,
|
||||
&item.FKMsDoctorID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to update patient visit: %w", err)
|
||||
}
|
||||
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (h *PatientVisitHandler) patchPatientVisit(ctx context.Context, dbConn *sql.DB, id int32, req *pasienVisit.TrPatientVisitPatchRequest) error {
|
||||
setParts := []string{}
|
||||
args := []interface{}{}
|
||||
argIndex := 1
|
||||
|
||||
|
||||
if req.Active != nil {
|
||||
setParts = append(setParts, "active = false")
|
||||
args = append(args, *req.Active)
|
||||
argIndex++
|
||||
}
|
||||
|
||||
if len(setParts) == 0 {
|
||||
return fmt.Errorf("no fields to update")
|
||||
}
|
||||
|
||||
query := fmt.Sprintf(`UPDATE "transaction".tr_patient_visit SET %s WHERE id = $%d`, strings.Join(setParts, ", "), argIndex)
|
||||
args = append(args, id)
|
||||
|
||||
result, err := dbConn.ExecContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to patch patient visit: %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 *PatientVisitHandler) deletePatientVisit(ctx context.Context, dbConn *sql.DB, id int32) error {
|
||||
query := `UPDATE "transaction".tr_patient_visit SET active = false WHERE id = $1`
|
||||
result, err := dbConn.ExecContext(ctx, query, id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete patient visit: %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 *PatientVisitHandler) fetchPatientVisits(ctx context.Context, dbConn *sql.DB, filter pasienVisit.TrPatientVisitFilter, limit, offset int) ([]pasienVisit.TrPatientVisit, error) {
|
||||
whereClause, args := h.buildWhereClause(filter)
|
||||
query := fmt.Sprintf(`SELECT id, barcode, registration_date, service_date, check_in_date, check_in, active, fk_ms_doctor_id
|
||||
FROM "transaction".tr_patient_visit
|
||||
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 patient visits query failed: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
items := make([]pasienVisit.TrPatientVisit, 0, limit)
|
||||
for rows.Next() {
|
||||
var item pasienVisit.TrPatientVisit
|
||||
err := rows.Scan(
|
||||
&item.ID,
|
||||
&item.Barcode,
|
||||
&item.RegistrationDate,
|
||||
&item.ServiceDate,
|
||||
&item.CheckInDate,
|
||||
&item.CheckIn,
|
||||
&item.Active,
|
||||
&item.FKMsDoctorID,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("scan patient visit failed: %w", err)
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, fmt.Errorf("rows iteration error: %w", err)
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func (h *PatientVisitHandler) getTotalCount(ctx context.Context, dbConn *sql.DB, filter pasienVisit.TrPatientVisitFilter, total *int) error {
|
||||
whereClause, args := h.buildWhereClause(filter)
|
||||
countQuery := fmt.Sprintf("SELECT COUNT(*) FROM \"transaction\".tr_patient_visit 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
|
||||
}
|
||||
|
||||
// --- Helper Methods ---
|
||||
func (h *PatientVisitHandler) 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 *PatientVisitHandler) 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 *PatientVisitHandler) 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 *PatientVisitHandler) parseFilterParams(c *gin.Context) pasienVisit.TrPatientVisitFilter {
|
||||
filter := pasienVisit.TrPatientVisitFilter{}
|
||||
|
||||
if activeStr := c.Query("active"); activeStr != "" {
|
||||
if active, err := strconv.ParseBool(activeStr); err == nil {
|
||||
filter.Active = &active
|
||||
}
|
||||
}
|
||||
if checkInStr := c.Query("registration_date"); checkInStr != "" {
|
||||
if checkIn, err := strconv.ParseBool(checkInStr); err == nil {
|
||||
filter.CheckIn = &checkIn
|
||||
}
|
||||
}
|
||||
if doctorStr := c.Query("fk_ms_doctor_id"); doctorStr != "" {
|
||||
if doctorID, err := strconv.ParseInt(doctorStr, 10, 32); err == nil {
|
||||
doctorID32 := int32(doctorID)
|
||||
filter.FKMsDoctorID = &doctorID32
|
||||
}
|
||||
}
|
||||
|
||||
// // Date range filter
|
||||
// if dateFromStr := c.Query("registration_date_from"); dateFromStr != "" {
|
||||
// if dateFrom, err := time.Parse("2006-01-02", dateFromStr); err == nil {
|
||||
// filter.RegistrationDateFrom = &dateFrom
|
||||
// }
|
||||
// }
|
||||
// if dateToStr := c.Query("registration_date_to"); dateToStr != "" {
|
||||
// if dateTo, err := time.Parse("2006-01-02", dateToStr); err == nil {
|
||||
// filter.RegistrationDateTo = &dateTo
|
||||
// }
|
||||
// }
|
||||
|
||||
if dateStr := c.Query("date"); dateStr != "" {
|
||||
if date, err := time.Parse("2006-01-02", dateStr); err == nil {
|
||||
startOfDay := time.Date(date.Year(), date.Month(), date.Day(), 0, 0, 0, 0, date.Location())
|
||||
endOfDay := time.Date(date.Year(), date.Month(), date.Day(), 23, 59, 59, 999999999, date.Location())
|
||||
|
||||
filter.RegistrationDateFrom = &startOfDay
|
||||
filter.RegistrationDateTo = &endOfDay
|
||||
return filter
|
||||
}
|
||||
}
|
||||
|
||||
return filter
|
||||
}
|
||||
|
||||
func (h *PatientVisitHandler) buildWhereClause(filter pasienVisit.TrPatientVisitFilter) (string, []interface{}) {
|
||||
conditions := []string{"1=1"}
|
||||
args := []interface{}{}
|
||||
paramCount := 1
|
||||
|
||||
if filter.RegistrationDateFrom != nil {
|
||||
conditions = append(conditions, fmt.Sprintf("registration_date >= $%d", paramCount))
|
||||
args = append(args, *filter.RegistrationDateFrom)
|
||||
paramCount++
|
||||
}
|
||||
if filter.RegistrationDateTo != nil {
|
||||
conditions = append(conditions, fmt.Sprintf("registration_date <= $%d", paramCount))
|
||||
args = append(args, *filter.RegistrationDateTo)
|
||||
paramCount++
|
||||
}
|
||||
|
||||
|
||||
return strings.Join(conditions, " AND "), args
|
||||
}
|
||||
|
||||
func (h *PatientVisitHandler) 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,
|
||||
}
|
||||
}
|
||||
203
internal/models/visit/visit.go
Normal file
203
internal/models/visit/visit.go
Normal file
@@ -0,0 +1,203 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"api-service/internal/models"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"time"
|
||||
)
|
||||
|
||||
// =================================================================================
|
||||
// 1. MODEL DATABASE (Core Struct)
|
||||
// =================================================================================
|
||||
|
||||
// TrPatientVisit mewakili tabel "transaction".tr_patient_visit
|
||||
type TrPatientVisit struct {
|
||||
ID int32 `json:"id" db:"id"`
|
||||
Barcode sql.NullInt16 `json:"barcode,omitempty" db:"barcode"`
|
||||
RegistrationDate sql.NullTime `json:"registration_date,omitempty" db:"registration_date"`
|
||||
ServiceDate sql.NullTime `json:"service_date,omitempty" db:"service_date"`
|
||||
CheckInDate sql.NullTime `json:"check_in_date,omitempty" db:"check_in_date"`
|
||||
CheckIn sql.NullBool `json:"check_in,omitempty" db:"check_in"`
|
||||
Active sql.NullBool `json:"active,omitempty" db:"active"`
|
||||
FKMsDoctorID sql.NullInt32 `json:"fk_ms_doctor_id,omitempty" db:"fk_ms_doctor_id"`
|
||||
}
|
||||
|
||||
// MarshalJSON adalah custom JSON marshaller untuk menghasilkan output yang bersih.
|
||||
func (t TrPatientVisit) MarshalJSON() ([]byte, error) {
|
||||
type Alias TrPatientVisit
|
||||
aux := &struct {
|
||||
Barcode *int16 `json:"barcode,omitempty"`
|
||||
RegistrationDate *time.Time `json:"registration_date,omitempty"`
|
||||
ServiceDate *time.Time `json:"service_date,omitempty"`
|
||||
CheckInDate *time.Time `json:"check_in_date,omitempty"`
|
||||
CheckIn *bool `json:"check_in,omitempty"`
|
||||
Active *bool `json:"active,omitempty"`
|
||||
FKMsDoctorID *int32 `json:"fk_ms_doctor_id,omitempty"`
|
||||
*Alias
|
||||
}{
|
||||
Alias: (*Alias)(&t),
|
||||
}
|
||||
|
||||
if t.Barcode.Valid {
|
||||
aux.Barcode = &t.Barcode.Int16
|
||||
}
|
||||
if t.RegistrationDate.Valid {
|
||||
aux.RegistrationDate = &t.RegistrationDate.Time
|
||||
}
|
||||
if t.ServiceDate.Valid {
|
||||
aux.ServiceDate = &t.ServiceDate.Time
|
||||
}
|
||||
if t.CheckInDate.Valid {
|
||||
aux.CheckInDate = &t.CheckInDate.Time
|
||||
}
|
||||
if t.CheckIn.Valid {
|
||||
aux.CheckIn = &t.CheckIn.Bool
|
||||
}
|
||||
if t.Active.Valid {
|
||||
aux.Active = &t.Active.Bool
|
||||
}
|
||||
if t.FKMsDoctorID.Valid {
|
||||
aux.FKMsDoctorID = &t.FKMsDoctorID.Int32
|
||||
}
|
||||
|
||||
return json.Marshal(aux)
|
||||
}
|
||||
|
||||
// --- Helper Methods untuk Akses Nilai yang Aman ---
|
||||
func (t *TrPatientVisit) GetBarcode() int16 {
|
||||
if t.Barcode.Valid {
|
||||
return t.Barcode.Int16
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (t *TrPatientVisit) GetRegistrationDate() time.Time {
|
||||
if t.RegistrationDate.Valid {
|
||||
return t.RegistrationDate.Time
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (t *TrPatientVisit) GetServiceDate() time.Time {
|
||||
if t.ServiceDate.Valid {
|
||||
return t.ServiceDate.Time
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (t *TrPatientVisit) GetCheckInDate() time.Time {
|
||||
if t.CheckInDate.Valid {
|
||||
return t.CheckInDate.Time
|
||||
}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
func (t *TrPatientVisit) GetCheckIn() bool {
|
||||
if t.CheckIn.Valid {
|
||||
return t.CheckIn.Bool
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *TrPatientVisit) GetActive() bool {
|
||||
if t.Active.Valid {
|
||||
return t.Active.Bool
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (t *TrPatientVisit) GetFKMsDoctorID() int32 {
|
||||
if t.FKMsDoctorID.Valid {
|
||||
return t.FKMsDoctorID.Int32
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
// =================================================================================
|
||||
// 2. MODEL UNTUK REQUEST & RESPONSE API
|
||||
// =================================================================================
|
||||
|
||||
// --- Response Models ---
|
||||
type TrPatientVisitGetResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data *TrPatientVisit `json:"data"`
|
||||
}
|
||||
|
||||
type TrPatientVisitGetListResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data []TrPatientVisit `json:"data"`
|
||||
Meta models.MetaResponse `json:"meta"`
|
||||
}
|
||||
|
||||
type TrPatientVisitCreateResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data *TrPatientVisit `json:"data"`
|
||||
}
|
||||
|
||||
type TrPatientVisitUpdateResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data *TrPatientVisit `json:"data"`
|
||||
}
|
||||
|
||||
type TrPatientVisitPatchResponse struct {
|
||||
Message string `json:"message"`
|
||||
Data *TrPatientVisit `json:"data"`
|
||||
}
|
||||
|
||||
type TrPatientVisitDeleteResponse struct {
|
||||
Message string `json:"message"`
|
||||
ID string `json:"id"`
|
||||
}
|
||||
|
||||
// --- Request Models ---
|
||||
|
||||
// TrPatientVisitCreateRequest adalah model untuk request POST (create)
|
||||
// Field yang nullable di DB menggunakan pointer agar opsional di request body.
|
||||
type TrPatientVisitCreateRequest struct {
|
||||
Barcode *int16 `json:"barcode,omitempty"`
|
||||
RegistrationDate *time.Time `json:"registration_date"`
|
||||
ServiceDate *time.Time `json:"service_date,omitempty"`
|
||||
CheckInDate *time.Time `json:"check_in_date,omitempty"`
|
||||
CheckIn *bool `json:"check_in,omitempty"`
|
||||
Active *bool `json:"active,omitempty"`
|
||||
FKMsDoctorID *int32 `json:"fk_ms_doctor_id,omitempty"`
|
||||
}
|
||||
|
||||
// TrPatientVisitUpdateRequest adalah model untuk request PUT (update penuh)
|
||||
// PUT biasanya mengharuskan semua field yang akan diupdate dikirim.
|
||||
// ID diambil dari path parameter.
|
||||
type TrPatientVisitUpdateRequest struct {
|
||||
ID int32 `json:"id"` // ID dari path
|
||||
Barcode *int16 `json:"barcode,omitempty"`
|
||||
RegistrationDate *time.Time `json:"registration_date"`
|
||||
ServiceDate *time.Time `json:"service_date,omitempty"`
|
||||
CheckInDate *time.Time `json:"check_in_date,omitempty"`
|
||||
CheckIn *bool `json:"check_in,omitempty"`
|
||||
Active *bool `json:"active,omitempty"`
|
||||
FKMsDoctorID *int32 `json:"fk_ms_doctor_id,omitempty"`
|
||||
}
|
||||
|
||||
// TrPatientVisitPatchRequest adalah model untuk request PATCH (update parsial)
|
||||
// PATCH hanya mengirim field yang ingin diubah.
|
||||
// Penggunaan pointer memungkinkan kita membedakan antara "tidak dikirim" dan "dikirim dengan nilai nol".
|
||||
type TrPatientVisitPatchRequest struct {
|
||||
Barcode *int16 `json:"barcode,omitempty"`
|
||||
RegistrationDate *time.Time `json:"registration_date,omitempty"`
|
||||
ServiceDate *time.Time `json:"service_date,omitempty"`
|
||||
CheckInDate *time.Time `json:"check_in_date,omitempty"`
|
||||
CheckIn *bool `json:"check_in,omitempty"`
|
||||
Active *bool `json:"active,omitempty"`
|
||||
FKMsDoctorID *int32 `json:"fk_ms_doctor_id,omitempty"`
|
||||
}
|
||||
|
||||
type TrPatientVisitFilter struct {
|
||||
Active *bool `json:"active,omitempty" form:"active"`
|
||||
CheckIn *bool `json:"check_in,omitempty" form:"check_in"`
|
||||
FKMsDoctorID *int32 `json:"fk_ms_doctor_id,omitempty" form:"fk_ms_doctor_id"`
|
||||
RegistrationDateFrom *time.Time `json:"registration_date_from,omitempty" form:"registration_date_from"`
|
||||
RegistrationDateTo *time.Time `json:"registration_date_to,omitempty" form:"registration_date_to"`
|
||||
Date *time.Time `json:"date,omitempty" form:"date"`
|
||||
Search *string `json:"search,omitempty" form:"search"`
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
pesertaHandlers "api-service/internal/handlers/peserta"
|
||||
registerCounterkHandlers "api-service/internal/handlers/registrasi"
|
||||
retribusiHandlers "api-service/internal/handlers/retribusi"
|
||||
patientVisitHandlers "api-service/internal/handlers/visit"
|
||||
"api-service/internal/handlers/websocket"
|
||||
websocketHandlers "api-service/internal/handlers/websocket"
|
||||
"api-service/internal/middleware"
|
||||
@@ -820,5 +821,14 @@ func RegisterRoutes(cfg *config.Config) *gin.Engine {
|
||||
regitsterCounterGroup.GET("/stats", registerCounterHandler.GetRegistrationCounterStats)
|
||||
}
|
||||
|
||||
patientVisitHandler := patientVisitHandlers.NewPatientVisitHandler()
|
||||
patientVisitGroup := v1.Group("/visit")
|
||||
{
|
||||
patientVisitGroup.GET("", patientVisitHandler.GetPatientVisits)
|
||||
patientVisitGroup.POST("", patientVisitHandler.CreatePatientVisit)
|
||||
patientVisitGroup.PATCH("/:id/cancel",patientVisitHandler.PatchPatientVisit )
|
||||
patientVisitGroup.PUT("/:id", patientVisitHandler.UpdatePatientVisit)
|
||||
}
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user