Initial CPONE middleware implementation
This commit is contained in:
131
internal/config/config.go
Normal file
131
internal/config/config.go
Normal file
@@ -0,0 +1,131 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
AppHost string
|
||||
AppPort string
|
||||
DBHost string
|
||||
DBPort string
|
||||
DBDatabase string
|
||||
DBUsername string
|
||||
DBPassword string
|
||||
BearerToken string
|
||||
DBMaxOpenConns int
|
||||
DBMaxIdleConns int
|
||||
DBConnMaxLifetime time.Duration
|
||||
}
|
||||
|
||||
func Load(path string) (Config, error) {
|
||||
if err := loadDotEnv(path); err != nil {
|
||||
return Config{}, err
|
||||
}
|
||||
|
||||
cfg := Config{
|
||||
AppHost: env("APP_HOST", "0.0.0.0"),
|
||||
AppPort: env("APP_PORT", "8080"),
|
||||
DBHost: env("DB_HOST", "127.0.0.1"),
|
||||
DBPort: env("DB_PORT", "3306"),
|
||||
DBDatabase: env("DB_DATABASE", ""),
|
||||
DBUsername: env("DB_USERNAME", "root"),
|
||||
DBPassword: env("DB_PASSWORD", ""),
|
||||
BearerToken: env("CPONE_BEARER_TOKEN", ""),
|
||||
DBMaxOpenConns: envInt("DB_MAX_OPEN_CONNS", 25),
|
||||
DBMaxIdleConns: envInt("DB_MAX_IDLE_CONNS", 25),
|
||||
DBConnMaxLifetime: time.Duration(envInt("DB_CONN_MAX_LIFETIME_MINUTES", 5)) * time.Minute,
|
||||
}
|
||||
|
||||
if cfg.DBDatabase == "" {
|
||||
return Config{}, fmt.Errorf("DB_DATABASE wajib diisi")
|
||||
}
|
||||
if cfg.BearerToken == "" {
|
||||
return Config{}, fmt.Errorf("CPONE_BEARER_TOKEN wajib diisi")
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
func (c Config) Addr() string {
|
||||
return c.AppHost + ":" + c.AppPort
|
||||
}
|
||||
|
||||
func (c Config) MySQLDSN() string {
|
||||
values := url.Values{}
|
||||
values.Set("charset", "utf8mb4")
|
||||
values.Set("parseTime", "true")
|
||||
values.Set("loc", "Local")
|
||||
|
||||
return fmt.Sprintf(
|
||||
"%s:%s@tcp(%s:%s)/%s?%s",
|
||||
c.DBUsername,
|
||||
c.DBPassword,
|
||||
c.DBHost,
|
||||
c.DBPort,
|
||||
c.DBDatabase,
|
||||
values.Encode(),
|
||||
)
|
||||
}
|
||||
|
||||
func loadDotEnv(path string) error {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
if line == "" || strings.HasPrefix(line, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
key, value, ok := strings.Cut(line, "=")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
key = strings.TrimSpace(key)
|
||||
value = strings.TrimSpace(value)
|
||||
value = strings.Trim(value, `"'`)
|
||||
|
||||
if key != "" {
|
||||
_ = os.Setenv(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
return scanner.Err()
|
||||
}
|
||||
|
||||
func env(key string, fallback string) string {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func envInt(key string, fallback int) int {
|
||||
value := os.Getenv(key)
|
||||
if value == "" {
|
||||
return fallback
|
||||
}
|
||||
|
||||
parsed, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
|
||||
return parsed
|
||||
}
|
||||
31
internal/db/db.go
Normal file
31
internal/db/db.go
Normal file
@@ -0,0 +1,31 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type DBTX interface {
|
||||
ExecContext(context.Context, string, ...interface{}) (sql.Result, error)
|
||||
PrepareContext(context.Context, string) (*sql.Stmt, error)
|
||||
QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error)
|
||||
QueryRowContext(context.Context, string, ...interface{}) *sql.Row
|
||||
}
|
||||
|
||||
func New(db DBTX) *Queries {
|
||||
return &Queries{db: db}
|
||||
}
|
||||
|
||||
type Queries struct {
|
||||
db DBTX
|
||||
}
|
||||
|
||||
func (q *Queries) WithTx(tx *sql.Tx) *Queries {
|
||||
return &Queries{
|
||||
db: tx,
|
||||
}
|
||||
}
|
||||
39
internal/db/models.go
Normal file
39
internal/db/models.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
type HargaLayanan10 struct {
|
||||
Hargalayananid int64 `json:"hargalayananid"`
|
||||
Layanandepartemenid sql.NullInt64 `json:"layanandepartemenid"`
|
||||
Kelasid sql.NullString `json:"kelasid"`
|
||||
Na sql.NullString `json:"na"`
|
||||
Harga sql.NullString `json:"harga"`
|
||||
Sharetipe sql.NullString `json:"sharetipe"`
|
||||
Sharedokter sql.NullString `json:"sharedokter"`
|
||||
Sharers sql.NullString `json:"sharers"`
|
||||
Shareexternal sql.NullString `json:"shareexternal"`
|
||||
Sharelain sql.NullString `json:"sharelain"`
|
||||
}
|
||||
|
||||
type Jenislayanan struct {
|
||||
Jenislayananid int64 `json:"jenislayananid"`
|
||||
Nama sql.NullString `json:"nama"`
|
||||
}
|
||||
|
||||
type Layanandeparteman struct {
|
||||
Layanandepartemenid int64 `json:"layanandepartemenid"`
|
||||
Layananid sql.NullString `json:"layananid"`
|
||||
Na sql.NullString `json:"na"`
|
||||
}
|
||||
|
||||
type Masterlayanan struct {
|
||||
Layananid string `json:"layananid"`
|
||||
Nama sql.NullString `json:"nama"`
|
||||
Jenislayananid sql.NullInt64 `json:"jenislayananid"`
|
||||
}
|
||||
135
internal/db/query.sql.go
Normal file
135
internal/db/query.sql.go
Normal file
@@ -0,0 +1,135 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: query.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const mappingHargaPeriode10 = `-- name: MappingHargaPeriode10 :many
|
||||
SELECT
|
||||
tarif.HargaLayananID AS TarifLayananID,
|
||||
layanan_dept.LayananID,
|
||||
layanan.Nama AS NamaLayanan,
|
||||
layanan.JenisLayananID,
|
||||
jenis.Nama AS NamaJenisLayanan,
|
||||
'Default' AS Kelas,
|
||||
'Default' AS NamaKelas,
|
||||
tarif.Harga,
|
||||
'Percentage' AS TipeDiskonRJ,
|
||||
'0.00' AS DiskonRJ,
|
||||
'Percentage' AS TipeDiskonRI,
|
||||
'0.00' AS DiskonRI,
|
||||
tarif.ShareTipe AS TipeShare,
|
||||
tarif.ShareDokter,
|
||||
tarif.ShareRS,
|
||||
tarif.ShareExternal AS ShareExt,
|
||||
tarif.ShareLain,
|
||||
'+Layanan' AS AsalTarif
|
||||
FROM layanandepartemen AS layanan_dept
|
||||
INNER JOIN harga_layanan_10 AS tarif
|
||||
ON tarif.LayananDepartemenID = layanan_dept.LayananDepartemenID
|
||||
AND tarif.KelasID = 'Default'
|
||||
AND tarif.NA = 'N'
|
||||
LEFT JOIN masterlayanan AS layanan
|
||||
ON layanan.LayananID = layanan_dept.LayananID
|
||||
LEFT JOIN jenislayanan AS jenis
|
||||
ON jenis.JenisLayananID = layanan.JenisLayananID
|
||||
WHERE layanan_dept.NA = 'N'
|
||||
AND layanan_dept.LayananID IN (/*SLICE:layanan_ids*/?)
|
||||
AND (
|
||||
? IS NULL
|
||||
OR layanan_dept.LayananID LIKE ?
|
||||
OR layanan.Nama LIKE ?
|
||||
)
|
||||
ORDER BY layanan.Nama, layanan.JenisLayananID
|
||||
`
|
||||
|
||||
type MappingHargaPeriode10Params struct {
|
||||
LayananIds []sql.NullString `json:"layanan_ids"`
|
||||
Search sql.NullString `json:"search"`
|
||||
}
|
||||
|
||||
type MappingHargaPeriode10Row struct {
|
||||
Tariflayananid int64 `json:"tariflayananid"`
|
||||
Layananid sql.NullString `json:"layananid"`
|
||||
Namalayanan sql.NullString `json:"namalayanan"`
|
||||
Jenislayananid sql.NullInt64 `json:"jenislayananid"`
|
||||
Namajenislayanan sql.NullString `json:"namajenislayanan"`
|
||||
Kelas string `json:"kelas"`
|
||||
Namakelas string `json:"namakelas"`
|
||||
Harga sql.NullString `json:"harga"`
|
||||
Tipediskonrj string `json:"tipediskonrj"`
|
||||
Diskonrj string `json:"diskonrj"`
|
||||
Tipediskonri string `json:"tipediskonri"`
|
||||
Diskonri string `json:"diskonri"`
|
||||
Tipeshare sql.NullString `json:"tipeshare"`
|
||||
Sharedokter sql.NullString `json:"sharedokter"`
|
||||
Sharers sql.NullString `json:"sharers"`
|
||||
Shareext sql.NullString `json:"shareext"`
|
||||
Sharelain sql.NullString `json:"sharelain"`
|
||||
Asaltarif string `json:"asaltarif"`
|
||||
}
|
||||
|
||||
// sqlc does not support dynamic table identifiers. This query is a generation
|
||||
// template for one concrete period table; duplicate or generate per period as
|
||||
// needed, then keep the HTTP response mapping unchanged.
|
||||
func (q *Queries) MappingHargaPeriode10(ctx context.Context, arg MappingHargaPeriode10Params) ([]MappingHargaPeriode10Row, error) {
|
||||
query := mappingHargaPeriode10
|
||||
var queryParams []interface{}
|
||||
if len(arg.LayananIds) > 0 {
|
||||
for _, v := range arg.LayananIds {
|
||||
queryParams = append(queryParams, v)
|
||||
}
|
||||
query = strings.Replace(query, "/*SLICE:layanan_ids*/?", strings.Repeat(",?", len(arg.LayananIds))[1:], 1)
|
||||
} else {
|
||||
query = strings.Replace(query, "/*SLICE:layanan_ids*/?", "NULL", 1)
|
||||
}
|
||||
queryParams = append(queryParams, arg.Search)
|
||||
queryParams = append(queryParams, arg.Search)
|
||||
queryParams = append(queryParams, arg.Search)
|
||||
rows, err := q.db.QueryContext(ctx, query, queryParams...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []MappingHargaPeriode10Row
|
||||
for rows.Next() {
|
||||
var i MappingHargaPeriode10Row
|
||||
if err := rows.Scan(
|
||||
&i.Tariflayananid,
|
||||
&i.Layananid,
|
||||
&i.Namalayanan,
|
||||
&i.Jenislayananid,
|
||||
&i.Namajenislayanan,
|
||||
&i.Kelas,
|
||||
&i.Namakelas,
|
||||
&i.Harga,
|
||||
&i.Tipediskonrj,
|
||||
&i.Diskonrj,
|
||||
&i.Tipediskonri,
|
||||
&i.Diskonri,
|
||||
&i.Tipeshare,
|
||||
&i.Sharedokter,
|
||||
&i.Sharers,
|
||||
&i.Shareext,
|
||||
&i.Sharelain,
|
||||
&i.Asaltarif,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
134
internal/http/handler.go
Normal file
134
internal/http/handler.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"primaya-api/cpone-middleware/internal/repository"
|
||||
)
|
||||
|
||||
var digitsOnly = regexp.MustCompile(`^\d+$`)
|
||||
|
||||
type LayananRepository interface {
|
||||
MappingHarga(ctx context.Context, params repository.MappingHargaParams) ([]repository.TarifLayanan, error)
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
repo LayananRepository
|
||||
}
|
||||
|
||||
func NewHandler(repo LayananRepository) Handler {
|
||||
return Handler{repo: repo}
|
||||
}
|
||||
|
||||
func (h Handler) MappingHarga(w http.ResponseWriter, r *http.Request) {
|
||||
layananIDs := parseLayananIDs(r)
|
||||
if len(layananIDs) == 0 {
|
||||
WriteJSON(w, http.StatusUnprocessableEntity, Response{
|
||||
Success: false,
|
||||
Message: "Parameter layananID wajib diisi dan dapat berupa satu atau beberapa LayananID",
|
||||
Data: nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
periodeTarifID, ok := firstQueryValue(r, "periodeTarifID", "periode_tarif_id")
|
||||
if !ok || strings.TrimSpace(periodeTarifID) == "" {
|
||||
WriteJSON(w, http.StatusUnprocessableEntity, Response{
|
||||
Success: false,
|
||||
Message: "Parameter periodeTarifID wajib diisi",
|
||||
Data: nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
periodeTarifID = strings.TrimSpace(periodeTarifID)
|
||||
if !digitsOnly.MatchString(periodeTarifID) {
|
||||
WriteJSON(w, http.StatusUnprocessableEntity, Response{
|
||||
Success: false,
|
||||
Message: "Parameter periodeTarifID harus berupa angka",
|
||||
Data: nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := h.repo.MappingHarga(r.Context(), repository.MappingHargaParams{
|
||||
PeriodeTarifID: periodeTarifID,
|
||||
LayananIDs: layananIDs,
|
||||
Search: strings.TrimSpace(r.URL.Query().Get("search")),
|
||||
})
|
||||
if err != nil {
|
||||
if errors.Is(err, repository.ErrTarifTableNotFound) {
|
||||
WriteJSON(w, http.StatusNotFound, Response{
|
||||
Success: false,
|
||||
Message: "Tabel tarif dasar tidak ditemukan: harga_layanan_" + periodeTarifID,
|
||||
Data: nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
WriteJSON(w, http.StatusInternalServerError, Response{
|
||||
Success: false,
|
||||
Message: "internal server error",
|
||||
Data: nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
WriteJSON(w, http.StatusOK, Response{
|
||||
Success: true,
|
||||
Message: "ok",
|
||||
Data: MappingHargaData{
|
||||
PeriodeTarifID: periodeTarifID,
|
||||
DetailLayanan: rows,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func parseLayananIDs(r *http.Request) []string {
|
||||
values := queryValues(r, "layananID", "layananID[]")
|
||||
if len(values) == 0 {
|
||||
values = queryValues(r, "layanan_id", "layanan_id[]")
|
||||
}
|
||||
|
||||
seen := make(map[string]struct{}, len(values))
|
||||
ids := make([]string, 0, len(values))
|
||||
|
||||
for _, value := range values {
|
||||
for _, part := range strings.Split(value, ",") {
|
||||
id := strings.TrimSpace(part)
|
||||
if id == "" {
|
||||
continue
|
||||
}
|
||||
if _, exists := seen[id]; exists {
|
||||
continue
|
||||
}
|
||||
seen[id] = struct{}{}
|
||||
ids = append(ids, id)
|
||||
}
|
||||
}
|
||||
|
||||
return ids
|
||||
}
|
||||
|
||||
func queryValues(r *http.Request, keys ...string) []string {
|
||||
query := r.URL.Query()
|
||||
values := make([]string, 0)
|
||||
for _, key := range keys {
|
||||
values = append(values, query[key]...)
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
func firstQueryValue(r *http.Request, keys ...string) (string, bool) {
|
||||
query := r.URL.Query()
|
||||
for _, key := range keys {
|
||||
if values, exists := query[key]; exists && len(values) > 0 {
|
||||
return values[0], true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
30
internal/http/middleware.go
Normal file
30
internal/http/middleware.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func BearerAuth(staticToken string, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if bearerToken(r.Header.Get("Authorization")) != staticToken {
|
||||
WriteJSON(w, http.StatusUnauthorized, Response{
|
||||
Success: false,
|
||||
Message: "Unauthenticated.",
|
||||
Data: nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func bearerToken(header string) string {
|
||||
const prefix = "Bearer "
|
||||
if !strings.HasPrefix(header, prefix) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.TrimSpace(strings.TrimPrefix(header, prefix))
|
||||
}
|
||||
25
internal/http/response.go
Normal file
25
internal/http/response.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"primaya-api/cpone-middleware/internal/repository"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
type MappingHargaData struct {
|
||||
PeriodeTarifID string `json:"PeriodeTarifID"`
|
||||
DetailLayanan []repository.TarifLayanan `json:"DetailLayanan"`
|
||||
}
|
||||
|
||||
func WriteJSON(w http.ResponseWriter, statusCode int, payload Response) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(statusCode)
|
||||
_ = json.NewEncoder(w).Encode(payload)
|
||||
}
|
||||
240
internal/repository/mysql.go
Normal file
240
internal/repository/mysql.go
Normal file
@@ -0,0 +1,240 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
var ErrTarifTableNotFound = errors.New("tarif table not found")
|
||||
|
||||
type MappingHargaParams struct {
|
||||
PeriodeTarifID string
|
||||
LayananIDs []string
|
||||
Search string
|
||||
}
|
||||
|
||||
type TarifLayanan struct {
|
||||
TarifLayananID int64 `json:"TarifLayananID"`
|
||||
LayananID *string `json:"LayananID"`
|
||||
NamaLayanan *string `json:"NamaLayanan"`
|
||||
JenisLayananID *int64 `json:"JenisLayananID"`
|
||||
NamaJenisLayanan *string `json:"NamaJenisLayanan"`
|
||||
Kelas string `json:"Kelas"`
|
||||
NamaKelas string `json:"NamaKelas"`
|
||||
Harga *string `json:"Harga"`
|
||||
TipeDiskonRJ string `json:"TipeDiskonRJ"`
|
||||
DiskonRJ string `json:"DiskonRJ"`
|
||||
TipeDiskonRI string `json:"TipeDiskonRI"`
|
||||
DiskonRI string `json:"DiskonRI"`
|
||||
TipeShare *string `json:"TipeShare"`
|
||||
ShareDokter *string `json:"ShareDokter"`
|
||||
ShareRS *string `json:"ShareRS"`
|
||||
ShareExt *string `json:"ShareExt"`
|
||||
ShareLain *string `json:"ShareLain"`
|
||||
AsalTarif string `json:"AsalTarif"`
|
||||
}
|
||||
|
||||
type MySQLLayananRepository struct {
|
||||
db *sql.DB
|
||||
}
|
||||
|
||||
func NewMySQLLayananRepository(db *sql.DB) MySQLLayananRepository {
|
||||
return MySQLLayananRepository{db: db}
|
||||
}
|
||||
|
||||
func (r MySQLLayananRepository) MappingHarga(ctx context.Context, params MappingHargaParams) ([]TarifLayanan, error) {
|
||||
tableName := "harga_layanan_" + params.PeriodeTarifID
|
||||
|
||||
exists, err := r.tableExists(ctx, tableName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, ErrTarifTableNotFound
|
||||
}
|
||||
|
||||
query, args := mappingHargaQuery(tableName, params.LayananIDs, params.Search)
|
||||
rows, err := r.db.QueryContext(ctx, query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
result := make([]TarifLayanan, 0)
|
||||
seenLayananID := map[string]struct{}{}
|
||||
|
||||
for rows.Next() {
|
||||
row, err := scanTarifLayanan(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
layananID := stringValue(row.LayananID)
|
||||
if layananID != "" {
|
||||
if _, exists := seenLayananID[layananID]; exists {
|
||||
continue
|
||||
}
|
||||
seenLayananID[layananID] = struct{}{}
|
||||
}
|
||||
|
||||
result = append(result, row)
|
||||
}
|
||||
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (r MySQLLayananRepository) tableExists(ctx context.Context, tableName string) (bool, error) {
|
||||
var count int
|
||||
err := r.db.QueryRowContext(ctx, `
|
||||
SELECT COUNT(*)
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = DATABASE()
|
||||
AND table_name = ?
|
||||
`, tableName).Scan(&count)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return count > 0, nil
|
||||
}
|
||||
|
||||
func mappingHargaQuery(tableName string, layananIDs []string, search string) (string, []any) {
|
||||
args := make([]any, 0, len(layananIDs)+2)
|
||||
placeholders := make([]string, 0, len(layananIDs))
|
||||
for _, id := range layananIDs {
|
||||
placeholders = append(placeholders, "?")
|
||||
args = append(args, id)
|
||||
}
|
||||
|
||||
var builder strings.Builder
|
||||
builder.WriteString(`
|
||||
SELECT
|
||||
tarif.HargaLayananID AS TarifLayananID,
|
||||
layanan_dept.LayananID,
|
||||
layanan.Nama AS NamaLayanan,
|
||||
layanan.JenisLayananID,
|
||||
jenis.Nama AS NamaJenisLayanan,
|
||||
'Default' AS Kelas,
|
||||
'Default' AS NamaKelas,
|
||||
tarif.Harga,
|
||||
'Percentage' AS TipeDiskonRJ,
|
||||
'0.00' AS DiskonRJ,
|
||||
'Percentage' AS TipeDiskonRI,
|
||||
'0.00' AS DiskonRI,
|
||||
tarif.ShareTipe AS TipeShare,
|
||||
tarif.ShareDokter,
|
||||
tarif.ShareRS,
|
||||
tarif.ShareExternal AS ShareExt,
|
||||
tarif.ShareLain,
|
||||
'+Layanan' AS AsalTarif
|
||||
FROM layanandepartemen AS layanan_dept
|
||||
INNER JOIN `)
|
||||
builder.WriteString(tableName)
|
||||
builder.WriteString(` AS tarif
|
||||
ON tarif.LayananDepartemenID = layanan_dept.LayananDepartemenID
|
||||
AND tarif.KelasID = 'Default'
|
||||
AND tarif.NA = 'N'
|
||||
LEFT JOIN masterlayanan AS layanan
|
||||
ON layanan.LayananID = layanan_dept.LayananID
|
||||
LEFT JOIN jenislayanan AS jenis
|
||||
ON jenis.JenisLayananID = layanan.JenisLayananID
|
||||
WHERE layanan_dept.NA = 'N'
|
||||
AND layanan_dept.LayananID IN (` + strings.Join(placeholders, ",") + `)
|
||||
`)
|
||||
|
||||
if search != "" {
|
||||
like := "%" + search + "%"
|
||||
builder.WriteString(`
|
||||
AND (
|
||||
layanan_dept.LayananID LIKE ?
|
||||
OR layanan.Nama LIKE ?
|
||||
)
|
||||
`)
|
||||
args = append(args, like, like)
|
||||
}
|
||||
|
||||
builder.WriteString(`
|
||||
ORDER BY layanan.Nama, layanan.JenisLayananID
|
||||
`)
|
||||
|
||||
return builder.String(), args
|
||||
}
|
||||
|
||||
func scanTarifLayanan(rows *sql.Rows) (TarifLayanan, error) {
|
||||
var row TarifLayanan
|
||||
var layananID sql.NullString
|
||||
var namaLayanan sql.NullString
|
||||
var jenisLayananID sql.NullInt64
|
||||
var namaJenisLayanan sql.NullString
|
||||
var harga sql.NullString
|
||||
var tipeShare sql.NullString
|
||||
var shareDokter sql.NullString
|
||||
var shareRS sql.NullString
|
||||
var shareExt sql.NullString
|
||||
var shareLain sql.NullString
|
||||
|
||||
err := rows.Scan(
|
||||
&row.TarifLayananID,
|
||||
&layananID,
|
||||
&namaLayanan,
|
||||
&jenisLayananID,
|
||||
&namaJenisLayanan,
|
||||
&row.Kelas,
|
||||
&row.NamaKelas,
|
||||
&harga,
|
||||
&row.TipeDiskonRJ,
|
||||
&row.DiskonRJ,
|
||||
&row.TipeDiskonRI,
|
||||
&row.DiskonRI,
|
||||
&tipeShare,
|
||||
&shareDokter,
|
||||
&shareRS,
|
||||
&shareExt,
|
||||
&shareLain,
|
||||
&row.AsalTarif,
|
||||
)
|
||||
if err != nil {
|
||||
return TarifLayanan{}, fmt.Errorf("scan tarif layanan: %w", err)
|
||||
}
|
||||
|
||||
row.LayananID = nullStringPtr(layananID)
|
||||
row.NamaLayanan = nullStringPtr(namaLayanan)
|
||||
row.JenisLayananID = nullInt64Ptr(jenisLayananID)
|
||||
row.NamaJenisLayanan = nullStringPtr(namaJenisLayanan)
|
||||
row.Harga = nullStringPtr(harga)
|
||||
row.TipeShare = nullStringPtr(tipeShare)
|
||||
row.ShareDokter = nullStringPtr(shareDokter)
|
||||
row.ShareRS = nullStringPtr(shareRS)
|
||||
row.ShareExt = nullStringPtr(shareExt)
|
||||
row.ShareLain = nullStringPtr(shareLain)
|
||||
|
||||
return row, nil
|
||||
}
|
||||
|
||||
func nullStringPtr(value sql.NullString) *string {
|
||||
if !value.Valid {
|
||||
return nil
|
||||
}
|
||||
return &value.String
|
||||
}
|
||||
|
||||
func nullInt64Ptr(value sql.NullInt64) *int64 {
|
||||
if !value.Valid {
|
||||
return nil
|
||||
}
|
||||
return &value.Int64
|
||||
}
|
||||
|
||||
func stringValue(value *string) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
}
|
||||
return *value
|
||||
}
|
||||
Reference in New Issue
Block a user