diff --git a/.gitignore b/.gitignore index 2eea525..e805e1f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ -.env \ No newline at end of file +.env +.air.toml +tmp/* \ No newline at end of file diff --git a/go.mod b/go.mod index 11cc2e7..0cdd7c0 100644 --- a/go.mod +++ b/go.mod @@ -16,6 +16,7 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/goccy/go-yaml v1.18.0 // indirect github.com/golang/snappy v0.0.4 // indirect + github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.8.0 // indirect diff --git a/go.sum b/go.sum index 57e3410..8af9aa7 100644 --- a/go.sum +++ b/go.sum @@ -29,6 +29,8 @@ github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7Lk github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= diff --git a/internal/domain/antrian_operasi/handler.go b/internal/domain/antrian_operasi/handler.go index 015f4a5..9fad65c 100644 --- a/internal/domain/antrian_operasi/handler.go +++ b/internal/domain/antrian_operasi/handler.go @@ -3,15 +3,29 @@ package antrianoperasi import ( "log" + dokter "antrian-operasi/internal/domain/reference/dokter" + kategori "antrian-operasi/internal/domain/reference/kategori" + spesialis "antrian-operasi/internal/domain/reference/spesialis" + "antrian-operasi/internal/shared" + "github.com/gin-gonic/gin" ) type AntrianOperasiHandler struct { - repo IAntrianOperasiRepository + repo IAntrianOperasiRepository + repoKategori kategori.IKategoriRepository + repoSpesialis spesialis.ISpesialisRepository + repoDokter dokter.IDokterRepository } -func NewAntrianOperasiHandler(repo IAntrianOperasiRepository) AntrianOperasiHandler { - return AntrianOperasiHandler{repo} +func NewAntrianOperasiHandler( + repo IAntrianOperasiRepository, + repoKategori kategori.IKategoriRepository, + repoSpesialis spesialis.ISpesialisRepository, + repoDokter dokter.IDokterRepository) AntrianOperasiHandler { + return AntrianOperasiHandler{ + repo, repoKategori, repoSpesialis, repoDokter, + } } func (h AntrianOperasiHandler) CreateAntrianOperasi(c *gin.Context) { @@ -19,15 +33,24 @@ func (h AntrianOperasiHandler) CreateAntrianOperasi(c *gin.Context) { // Binding format JSON if err := c.ShouldBindJSON(&req); err != nil { log.Printf("error bind json : %s", err) - c.JSON(500, err) + c.JSON(500, shared.BaseErrorResponse{ + Success: false, + Code: 500, + Message: "error bind json", + }) return } // Request data master validation - errValidation := req.DataValidation() - if errValidation != nil { + isValid, errValidation := req.DataValidation(c, h) + if isValid == false { log.Printf("validation error : %s", errValidation) - c.JSON(500, errValidation) + c.JSON(500, shared.BaseErrorResponse{ + Success: false, + Code: 500, + Message: "validation error", + Errors: errValidation, + }) return } @@ -35,9 +58,13 @@ func (h AntrianOperasiHandler) CreateAntrianOperasi(c *gin.Context) { res, err := h.repo.CreateAntrianOperasi(c, req) if err != nil { log.Printf("insert error : %s", err) - c.JSON(500, err) + c.JSON(500, shared.BaseErrorResponse{ + Success: false, + Code: 500, + Message: "insert error", + }) return } - c.JSON(201, res) + c.JSON(201, shared.ToBaseResponse(res, true, 201, "success insert antrian operasi")) } diff --git a/internal/domain/antrian_operasi/repository.go b/internal/domain/antrian_operasi/repository.go index e02a7f5..5da9492 100644 --- a/internal/domain/antrian_operasi/repository.go +++ b/internal/domain/antrian_operasi/repository.go @@ -4,8 +4,10 @@ import ( "antrian-operasi/internal/database" queryUtils "antrian-operasi/internal/utils/query" "log" + "time" "github.com/gin-gonic/gin" + "github.com/google/uuid" ) const DB_NAME = "db_antrian" @@ -20,9 +22,17 @@ type antrianOperasiRepo struct { db database.Service } -func NewAntrianOperasiRepo(dbService database.Service) IAntrianOperasiRepository { +func NewRepository(dbService database.Service) IAntrianOperasiRepository { queryBuilder := queryUtils.NewQueryBuilder(queryUtils.DBTypePostgreSQL). - SetAllowedColumns([]string{}) + SetAllowedColumns([]string{ + "id", "status", "date_created", "\"No_rekam_medis\"", "\"No_KTP\"", "\"Nama_pasien\"", + "\"Jenis_kelamin\"", "\"Tanggal_lahir\"", "\"Umur\"", "\"Alamat\"", "\"Tanggal_daftar\"", + "\"Kategori_operasi\"", "\"Rencana_operasi\"", "\"Status_operasi\"", "\"Nomor\"", + "\"Spesialis\"", "\"Sub_spesialis\"", "\"Keterangan_status_pasien\"", + "\"Nomor_spesialis\"", "\"Nomor_sub_spesialis\"", + "\"Nomor_telepon\"", + "\"FK_pasien_operasi_telepon_pasien_operasi_ID\"", + }) queryBuilder.SetSecurityOptions(false, 100) return antrianOperasiRepo{ @@ -45,6 +55,96 @@ func (r antrianOperasiRepo) CreateAntrianOperasi(c *gin.Context, req CreatePasie // INSERTING log.Println("TODO : insert tables") + // INSERT MAIN TABLE : data_pasien_operasi + idAntrian := uuid.New().String() + insertMainQuery := queryUtils.InsertData{ + Columns: []string{ + "id", "status", "date_created", "\"No_rekam_medis\"", "\"No_KTP\"", "\"Nama_pasien\"", + "\"Jenis_kelamin\"", "\"Tanggal_lahir\"", "\"Umur\"", "\"Alamat\"", "\"Tanggal_daftar\"", + "\"Kategori_operasi\"", "\"Rencana_operasi\"", "\"Status_operasi\"", "\"Nomor\"", + "\"Spesialis\"", "\"Sub_spesialis\"", "\"Keterangan_status_pasien\"", + "\"Nomor_spesialis\"", "\"Nomor_sub_spesialis\"", + }, Values: []interface{}{ + idAntrian, + req.StatusPasienData.StatusOperasi, + time.Now(), + req.FormData.NoRekamMedis, + req.FormData.NoKtp, + req.FormData.NamaPasien, + req.FormData.JenisKelamin, + req.FormData.TglLahir, + req.FormData.Umur, + req.FormData.Alamat, + req.RencanaOperasiData.TanggalDaftar, + req.RencanaOperasiData.KategoriOperasi, + req.RencanaOperasiData.RencanaOperasi, + req.StatusPasienData.StatusOperasi, + 1, // nomor + req.RencanaOperasiData.Spesialis, + req.RencanaOperasiData.SubSpesialis, + req.RencanaOperasiData.Keterangan, + req.RencanaOperasiData.Spesialis, + req.RencanaOperasiData.SubSpesialis, + }, + } + returningCols := []string{ + "id", + } + + // _, err = r.queryBuilder.ExecuteInsert(c, db, "data_pasien_operasi", insertMainQuery, returningCols...) + sql, args, err := r.queryBuilder.BuildInsertQuery("data_pasien_operasi", insertMainQuery, returningCols...) + log.Printf("query main : %s", sql) + _, err = tx.ExecContext(c, sql, args...) + if err != nil { + tx.Rollback() + log.Println(err) + return req, err + } + log.Printf("success insert main : %s", insertMainQuery.Values...) + + // INSERT data_telepon_pasien_operasi + var valuesInsertTelepon []interface{} + + for _, telp := range *req.FormData.NoTelepon { + idTelepon := uuid.New().String() + itemValues := []string{idTelepon, telp, idAntrian} + valuesInsertTelepon = append(valuesInsertTelepon, itemValues) + } + + insertTeleponQuery := queryUtils.InsertData{ + Columns: []string{ + "id", + "\"Nomor_telepon\"", + "\"FK_pasien_operasi_telepon_pasien_operasi_ID\"", + }, + Values: valuesInsertTelepon, + } + + // _, err = r.queryBuilder.ExecuteInsert(c, db, "data_telepon_pasien_operasi", insertTeleponQuery, returningCols...) + sql, args, err = r.queryBuilder.BuildBulkInsertQuery("data_telepon_pasien_operasi", insertTeleponQuery, returningCols...) + if err != nil { + log.Printf("error building query telepon %s", err) + tx.Rollback() + + return req, err + } + log.Printf("query telepon : %s", sql) + log.Printf("args telepon : %s", args...) + _, err = tx.ExecContext(c, sql, args...) + if err != nil { + tx.Rollback() + + log.Println(err) + + return req, err + } + log.Printf("success insert telepon") + + // INSERT data_diagnosa_pasien_operasi + + // INSERT data_tindakan_pasien_operasi + + // INSERT data_pasien_operasi_data_pegawai // COMMIT TRANSACTION tx.Commit() diff --git a/internal/domain/antrian_operasi/request.go b/internal/domain/antrian_operasi/request.go index b5f9082..31b1fd7 100644 --- a/internal/domain/antrian_operasi/request.go +++ b/internal/domain/antrian_operasi/request.go @@ -37,7 +37,7 @@ type RencanaOperasiRequest struct { } type DokterPelaksanaItemRequest struct { - Id *int `json:"id"` + Id string `json:"id"` Nip *string `json:"nip"` Nama *string `json:"nama"` SatuanKerja *string `json:"satuan_kerja"` diff --git a/internal/domain/antrian_operasi/requestValidation.go b/internal/domain/antrian_operasi/requestValidation.go index ef70ebb..e5067b3 100644 --- a/internal/domain/antrian_operasi/requestValidation.go +++ b/internal/domain/antrian_operasi/requestValidation.go @@ -1,8 +1,44 @@ package antrianoperasi -import "log" +import ( + "log" -func (req CreatePasienOperasiRequest) DataValidation() error { - log.Println("TODO : Data Validation") - return nil + "github.com/gin-gonic/gin" +) + +func (req CreatePasienOperasiRequest) DataValidation(c *gin.Context, handler AntrianOperasiHandler) (bool, []string) { + isValid := false + var errValidation []string + + // validasi kategori + _, err := handler.repoKategori.GetKategoriById(c, req.RencanaOperasiData.KategoriOperasi) + if err != nil { + log.Println(err) + errValidation = append(errValidation, err.Error()) + } + _, err = handler.repoSpesialis.GetSpesialisById(c, req.RencanaOperasiData.Spesialis) + if err != nil { + log.Println(err) + errValidation = append(errValidation, err.Error()) + } + _, err = handler.repoSpesialis.GetSubSpesialisById(c, req.RencanaOperasiData.SubSpesialis) + if err != nil { + log.Println(err) + errValidation = append(errValidation, err.Error()) + } + + for _, dp := range req.DokterPelaksanaItems { + _, err := handler.repoDokter.GetDokterById(c, dp.Id) + if err != nil { + log.Println(err) + errValidation = append(errValidation, err.Error()) + continue + } + } + + if len(errValidation) == 0 { + isValid = true + } + + return isValid, errValidation } diff --git a/internal/domain/antrian_operasi/routes.go b/internal/domain/antrian_operasi/routes.go index 9518753..4e40bc6 100644 --- a/internal/domain/antrian_operasi/routes.go +++ b/internal/domain/antrian_operasi/routes.go @@ -2,13 +2,21 @@ package antrianoperasi import ( "antrian-operasi/internal/database" + "antrian-operasi/internal/domain/reference/dokter" + "antrian-operasi/internal/domain/reference/kategori" + "antrian-operasi/internal/domain/reference/spesialis" "github.com/gin-gonic/gin" ) func RegisterRoutes(r *gin.RouterGroup, dbService database.Service) { - antrianOperasiRepo := NewAntrianOperasiRepo(dbService) - antrianOperasiHandler := NewAntrianOperasiHandler(antrianOperasiRepo) + antrianOperasiRepo := NewRepository(dbService) + kategoriRepo := kategori.NewRepository(dbService) + spesialisRepo := spesialis.NewRepository(dbService) + dokterRepo := dokter.NewRepository(dbService) + + antrianOperasiHandler := NewAntrianOperasiHandler( + antrianOperasiRepo, kategoriRepo, spesialisRepo, dokterRepo) r.POST("/antrian-operasi", antrianOperasiHandler.CreateAntrianOperasi) } diff --git a/internal/shared/baseResponse.go b/internal/shared/baseResponse.go index 6624249..8d68b51 100644 --- a/internal/shared/baseResponse.go +++ b/internal/shared/baseResponse.go @@ -1,5 +1,12 @@ package shared +type BaseErrorResponse struct { + Success bool `json:"success"` + Code int `json:"code"` + Message string `json:"message"` + Errors []string `json:"errors"` +} + type BaseResponse[T any] struct { Success bool `json:"success"` Code int `json:"code"` diff --git a/internal/utils/query/builder.go b/internal/utils/query/builder.go index c0305a3..4981bde 100644 --- a/internal/utils/query/builder.go +++ b/internal/utils/query/builder.go @@ -1332,6 +1332,7 @@ func (qb *QueryBuilder) ExecuteInsert(ctx context.Context, db *sqlx.DB, table st start := time.Now() result, err := db.ExecContext(ctx, sql, args...) if qb.enableQueryLogging { + fmt.Printf("[DEBUG BuilderQuery] Final SQL query: %s\n", sql) fmt.Printf("[DEBUG] Insert query executed in %v\n", time.Since(start)) } return result, err @@ -1712,6 +1713,39 @@ func (qb *QueryBuilder) BuildInsertQuery(table string, data InsertData, returnin return sql, args, nil } +func (qb *QueryBuilder) BuildBulkInsertQuery(table string, data InsertData, returningColumns ...string) (string, []interface{}, error) { + // Validate columns + for _, col := range data.Columns { + if qb.allowedColumns != nil && !qb.allowedColumns[col] { + return "", nil, fmt.Errorf("disallowed column: %s", col) + } + } + + // Start with basic insert + insert := qb.sqlBuilder.Insert(table).Columns(data.Columns...) + + // loop insert data values + for _, item := range data.Values { + log.Printf("%s", item) + insert.Values(item) + } + + if len(returningColumns) > 0 { + if qb.dbType == DBTypePostgreSQL { + insert = insert.Suffix("RETURNING " + strings.Join(returningColumns, ", ")) + } else { + return "", nil, fmt.Errorf("RETURNING not supported for database type: %s", qb.dbType) + } + } + + sql, args, err := insert.ToSql() + if err != nil { + return "", nil, fmt.Errorf("failed to build INSERT query: %w", err) + } + + return sql, args, nil +} + // BuildUpdateQuery builds an UPDATE query func (qb *QueryBuilder) BuildUpdateQuery(table string, updateData UpdateData, filters []FilterGroup, returningColumns ...string) (string, []interface{}, error) { // Validate columns