pembuatan service crud visit register pasien
Some checks failed
Go-test / build (push) Has been cancelled

This commit is contained in:
2025-12-01 15:40:11 +07:00
parent 4cb977ddc7
commit 7b6987ebbd
3 changed files with 862 additions and 0 deletions

View 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(&currentActiveStatus)
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,
}
}

View 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"`
}

View File

@@ -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
}