feat: add search and pagination to tariff period services
This commit is contained in:
15
README.md
15
README.md
@@ -23,6 +23,21 @@ Authorization: Bearer <your-secure-token>
|
|||||||
GET /api/cpone/tarif-kerjasama/{id}?group_tarif_id=&active=N&effective_date=2026-08-22
|
GET /api/cpone/tarif-kerjasama/{id}?group_tarif_id=&active=N&effective_date=2026-08-22
|
||||||
Authorization: Bearer <your-secure-token>
|
Authorization: Bearer <your-secure-token>
|
||||||
|
|
||||||
|
GET /api/cpone/periode-tarif
|
||||||
|
Authorization: Bearer <your-secure-token>
|
||||||
|
X-RS-Code: <kode-rs>
|
||||||
|
|
||||||
|
Endpoint ini menampilkan seluruh periode tarif dari HIS dengan `NA = 'N'`.
|
||||||
|
|
||||||
|
GET /api/cpone/periode-tarif/{PeriodeTarifID}?search=&page=1
|
||||||
|
Authorization: Bearer <your-secure-token>
|
||||||
|
X-RS-Code: <kode-rs>
|
||||||
|
|
||||||
|
Endpoint ini mengambil detail seluruh tarif layanan dari tabel dinamis
|
||||||
|
`harga_layanan_{PeriodeTarifID}` beserta layanan, departemen, dan kelas tarif.
|
||||||
|
Parameter `search` mencari berdasarkan `LayananID` atau `NamaLayanan`; hasil
|
||||||
|
dipaginasi 20 data per halaman.
|
||||||
|
|
||||||
GET /api/cpone/layanan-mapping-harga?periodeTarifID=10&layananID=6413,6414,1309
|
GET /api/cpone/layanan-mapping-harga?periodeTarifID=10&layananID=6413,6414,1309
|
||||||
Authorization: Bearer <your-secure-token>
|
Authorization: Bearer <your-secure-token>
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,8 @@ func main() {
|
|||||||
mux.Handle("GET /api/cpone/perusahaan/{id}/group-tarif", protected(http.HandlerFunc(handler.GetPerusahaanGroupTarif)))
|
mux.Handle("GET /api/cpone/perusahaan/{id}/group-tarif", protected(http.HandlerFunc(handler.GetPerusahaanGroupTarif)))
|
||||||
mux.Handle("GET /api/cpone/tarif-kerjasama", protected(http.HandlerFunc(handler.ListTarifKerjasama)))
|
mux.Handle("GET /api/cpone/tarif-kerjasama", protected(http.HandlerFunc(handler.ListTarifKerjasama)))
|
||||||
mux.Handle("GET /api/cpone/tarif-kerjasama/{id}", protected(http.HandlerFunc(handler.GetTarifKerjasama)))
|
mux.Handle("GET /api/cpone/tarif-kerjasama/{id}", protected(http.HandlerFunc(handler.GetTarifKerjasama)))
|
||||||
|
mux.Handle("GET /api/cpone/periode-tarif", protected(http.HandlerFunc(handler.ListPeriodeTarif)))
|
||||||
|
mux.Handle("GET /api/cpone/periode-tarif/{periodeTarifID}", protected(http.HandlerFunc(handler.ListPeriodeTarifLayanan)))
|
||||||
mux.Handle("GET /api/cpone/layanan-mapping-harga", protected(http.HandlerFunc(handler.MappingHarga)))
|
mux.Handle("GET /api/cpone/layanan-mapping-harga", protected(http.HandlerFunc(handler.MappingHarga)))
|
||||||
mux.Handle("POST /api/cpone/patients/medrec", protected(http.HandlerFunc(handler.StoreMedrec)))
|
mux.Handle("POST /api/cpone/patients/medrec", protected(http.HandlerFunc(handler.StoreMedrec)))
|
||||||
mux.Handle("POST /api/cpone/patients/lab-registration", protected(http.HandlerFunc(handler.RegisterLab)))
|
mux.Handle("POST /api/cpone/patients/lab-registration", protected(http.HandlerFunc(handler.RegisterLab)))
|
||||||
|
|||||||
@@ -41,6 +41,14 @@ type TarifKerjasamaRepository interface {
|
|||||||
GetTarifKerjasama(ctx context.Context, id int64, params repository.TarifKerjasamaDetailParams) (*repository.TarifKerjasamaDetail, error)
|
GetTarifKerjasama(ctx context.Context, id int64, params repository.TarifKerjasamaDetailParams) (*repository.TarifKerjasamaDetail, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PeriodeTarifRepository interface {
|
||||||
|
ListPeriodeTarif(ctx context.Context) ([]repository.PeriodeTarif, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type PeriodeTarifDetailRepository interface {
|
||||||
|
ListPeriodeTarifLayanan(ctx context.Context, params repository.PeriodeTarifLayananListParams) (repository.PeriodeTarifLayananPage, error)
|
||||||
|
}
|
||||||
|
|
||||||
type PerusahaanRepository interface {
|
type PerusahaanRepository interface {
|
||||||
ListPerusahaan(ctx context.Context, params repository.PerusahaanListParams) (repository.PerusahaanPage, error)
|
ListPerusahaan(ctx context.Context, params repository.PerusahaanListParams) (repository.PerusahaanPage, error)
|
||||||
GetPerusahaan(ctx context.Context, id string) (*repository.PerusahaanDetail, error)
|
GetPerusahaan(ctx context.Context, id string) (*repository.PerusahaanDetail, error)
|
||||||
@@ -54,6 +62,8 @@ type Handler struct {
|
|||||||
labResultRepo LabResultRepository
|
labResultRepo LabResultRepository
|
||||||
paketRepo PaketRepository
|
paketRepo PaketRepository
|
||||||
tarifRepo TarifKerjasamaRepository
|
tarifRepo TarifKerjasamaRepository
|
||||||
|
periodeTarifRepo PeriodeTarifRepository
|
||||||
|
periodeTarifDetailRepo PeriodeTarifDetailRepository
|
||||||
perusahaanRepo PerusahaanRepository
|
perusahaanRepo PerusahaanRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,13 +74,16 @@ type CPONERepository interface {
|
|||||||
LabResultRepository
|
LabResultRepository
|
||||||
PaketRepository
|
PaketRepository
|
||||||
TarifKerjasamaRepository
|
TarifKerjasamaRepository
|
||||||
|
PeriodeTarifRepository
|
||||||
|
PeriodeTarifDetailRepository
|
||||||
PerusahaanRepository
|
PerusahaanRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewHandler(repo CPONERepository) Handler {
|
func NewHandler(repo CPONERepository) Handler {
|
||||||
return Handler{
|
return Handler{
|
||||||
layananRepo: repo, labRepo: repo, patientRepo: repo,
|
layananRepo: repo, labRepo: repo, patientRepo: repo,
|
||||||
labResultRepo: repo, paketRepo: repo, tarifRepo: repo, perusahaanRepo: repo,
|
labResultRepo: repo, paketRepo: repo, tarifRepo: repo,
|
||||||
|
periodeTarifRepo: repo, periodeTarifDetailRepo: repo, perusahaanRepo: repo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -118,6 +131,20 @@ func (h Handler) tarifRepository(ctx context.Context) TarifKerjasamaRepository {
|
|||||||
return h.tarifRepo
|
return h.tarifRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h Handler) periodeTarifRepository(ctx context.Context) PeriodeTarifRepository {
|
||||||
|
if repo, ok := selectedRepository(ctx); ok {
|
||||||
|
return repo
|
||||||
|
}
|
||||||
|
return h.periodeTarifRepo
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) periodeTarifDetailRepository(ctx context.Context) PeriodeTarifDetailRepository {
|
||||||
|
if repo, ok := selectedRepository(ctx); ok {
|
||||||
|
return repo
|
||||||
|
}
|
||||||
|
return h.periodeTarifDetailRepo
|
||||||
|
}
|
||||||
|
|
||||||
func (h Handler) perusahaanRepository(ctx context.Context) PerusahaanRepository {
|
func (h Handler) perusahaanRepository(ctx context.Context) PerusahaanRepository {
|
||||||
if repo, ok := selectedRepository(ctx); ok {
|
if repo, ok := selectedRepository(ctx); ok {
|
||||||
return repo
|
return repo
|
||||||
|
|||||||
29
internal/http/periode_tarif.go
Normal file
29
internal/http/periode_tarif.go
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"primaya-api/cpone-middleware/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h Handler) ListPeriodeTarif(w http.ResponseWriter, r *http.Request) {
|
||||||
|
items, err := h.periodeTarifRepository(r.Context()).ListPeriodeTarif(r.Context())
|
||||||
|
if err != nil {
|
||||||
|
WriteJSON(w, http.StatusInternalServerError, Response{
|
||||||
|
Success: false,
|
||||||
|
Message: "internal server error",
|
||||||
|
Data: nil,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if items == nil {
|
||||||
|
items = []repository.PeriodeTarif{}
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteJSON(w, http.StatusOK, Response{
|
||||||
|
Success: true,
|
||||||
|
Message: "ok",
|
||||||
|
Data: items,
|
||||||
|
})
|
||||||
|
}
|
||||||
118
internal/http/periode_tarif_detail.go
Normal file
118
internal/http/periode_tarif_detail.go
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"primaya-api/cpone-middleware/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
const periodeTarifLayananPerPage = 20
|
||||||
|
|
||||||
|
type PeriodeTarifLayananPaginationResponse struct {
|
||||||
|
CurrentPage int `json:"current_page"`
|
||||||
|
Data []repository.PeriodeTarifLayanan `json:"data"`
|
||||||
|
FirstPageURL string `json:"first_page_url"`
|
||||||
|
From *int `json:"from"`
|
||||||
|
LastPage int `json:"last_page"`
|
||||||
|
LastPageURL string `json:"last_page_url"`
|
||||||
|
Links map[string]any `json:"links"`
|
||||||
|
NextPageURL *string `json:"next_page_url"`
|
||||||
|
Total int `json:"total"`
|
||||||
|
PerPage int `json:"per_page"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h Handler) ListPeriodeTarifLayanan(w http.ResponseWriter, r *http.Request) {
|
||||||
|
rawID := strings.TrimSpace(r.PathValue("periodeTarifID"))
|
||||||
|
if rawID == "" {
|
||||||
|
rawID = strings.TrimSpace(r.PathValue("PeriodeTarifID"))
|
||||||
|
}
|
||||||
|
periodeTarifID, err := strconv.ParseInt(rawID, 10, 64)
|
||||||
|
if err != nil || periodeTarifID < 1 {
|
||||||
|
WriteJSON(w, http.StatusUnprocessableEntity, Response{
|
||||||
|
Success: false,
|
||||||
|
Message: "PeriodeTarifID harus berupa angka positif",
|
||||||
|
Data: nil,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pageNumber := periodeTarifLayananPageNumber(r)
|
||||||
|
search := strings.TrimSpace(r.URL.Query().Get("search"))
|
||||||
|
page, err := h.periodeTarifDetailRepository(r.Context()).ListPeriodeTarifLayanan(r.Context(), repository.PeriodeTarifLayananListParams{
|
||||||
|
PeriodeTarifID: periodeTarifID,
|
||||||
|
Page: pageNumber,
|
||||||
|
PerPage: periodeTarifLayananPerPage,
|
||||||
|
Search: search,
|
||||||
|
})
|
||||||
|
if errors.Is(err, repository.ErrTarifTableNotFound) {
|
||||||
|
WriteJSON(w, http.StatusNotFound, Response{
|
||||||
|
Success: false,
|
||||||
|
Message: "Tabel tarif tidak ditemukan: harga_layanan_" + strconv.FormatInt(periodeTarifID, 10),
|
||||||
|
Data: nil,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
WriteJSON(w, http.StatusInternalServerError, Response{
|
||||||
|
Success: false,
|
||||||
|
Message: "internal server error",
|
||||||
|
Data: nil,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if page.Items == nil {
|
||||||
|
page.Items = []repository.PeriodeTarifLayanan{}
|
||||||
|
}
|
||||||
|
|
||||||
|
lastPage := (page.Total + periodeTarifLayananPerPage - 1) / periodeTarifLayananPerPage
|
||||||
|
if lastPage < 1 {
|
||||||
|
lastPage = 1
|
||||||
|
}
|
||||||
|
var from *int
|
||||||
|
if len(page.Items) > 0 {
|
||||||
|
value := (pageNumber-1)*periodeTarifLayananPerPage + 1
|
||||||
|
from = &value
|
||||||
|
}
|
||||||
|
var nextPageURL *string
|
||||||
|
if pageNumber < lastPage {
|
||||||
|
value := periodeTarifLayananPageURL(r, pageNumber+1)
|
||||||
|
nextPageURL = &value
|
||||||
|
}
|
||||||
|
|
||||||
|
WriteJSON(w, http.StatusOK, PeriodeTarifLayananPaginationResponse{
|
||||||
|
CurrentPage: pageNumber, Data: page.Items, FirstPageURL: "", From: from,
|
||||||
|
LastPage: lastPage, LastPageURL: "", Links: map[string]any{},
|
||||||
|
NextPageURL: nextPageURL, Total: page.Total, PerPage: periodeTarifLayananPerPage,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func periodeTarifLayananPageNumber(r *http.Request) int {
|
||||||
|
page, err := strconv.Atoi(r.URL.Query().Get("page"))
|
||||||
|
if err != nil || page < 1 {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return page
|
||||||
|
}
|
||||||
|
|
||||||
|
func periodeTarifLayananPageURL(r *http.Request, page int) string {
|
||||||
|
scheme := "http"
|
||||||
|
if r.TLS != nil {
|
||||||
|
scheme = "https"
|
||||||
|
}
|
||||||
|
if forwarded := r.Header.Get("X-Forwarded-Proto"); forwarded != "" {
|
||||||
|
scheme = strings.TrimSpace(strings.Split(forwarded, ",")[0])
|
||||||
|
}
|
||||||
|
host := r.Host
|
||||||
|
if host == "" {
|
||||||
|
host = "localhost"
|
||||||
|
}
|
||||||
|
query := r.URL.Query()
|
||||||
|
query.Set("page", strconv.Itoa(page))
|
||||||
|
query.Set("per_page", strconv.Itoa(periodeTarifLayananPerPage))
|
||||||
|
return (&url.URL{Scheme: scheme, Host: host, Path: r.URL.Path, RawQuery: query.Encode()}).String()
|
||||||
|
}
|
||||||
113
internal/http/periode_tarif_detail_test.go
Normal file
113
internal/http/periode_tarif_detail_test.go
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"primaya-api/cpone-middleware/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakePeriodeTarifDetailRepository struct {
|
||||||
|
items []repository.PeriodeTarifLayanan
|
||||||
|
err error
|
||||||
|
id int64
|
||||||
|
params repository.PeriodeTarifLayananListParams
|
||||||
|
total int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakePeriodeTarifDetailRepository) ListPeriodeTarifLayanan(_ context.Context, params repository.PeriodeTarifLayananListParams) (repository.PeriodeTarifLayananPage, error) {
|
||||||
|
f.id = params.PeriodeTarifID
|
||||||
|
f.params = params
|
||||||
|
total := f.total
|
||||||
|
if total == 0 {
|
||||||
|
total = len(f.items)
|
||||||
|
}
|
||||||
|
return repository.PeriodeTarifLayananPage{Items: f.items, Total: total}, f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListPeriodeTarifLayananReturnsRows(t *testing.T) {
|
||||||
|
layananID, nama, departemen, kelas, namaKelas, harga := "6413", "Darah Lengkap", "LAB", "Default", "Default", "125000.00"
|
||||||
|
repo := &fakePeriodeTarifDetailRepository{items: []repository.PeriodeTarifLayanan{{
|
||||||
|
TarifLayananID: 10, LayananID: &layananID, NamaLayanan: &nama,
|
||||||
|
LayananDepartemenID: 20, Departemen: &departemen, Kelas: kelas, NamaKelas: &namaKelas,
|
||||||
|
Harga: &harga,
|
||||||
|
}}}
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/api/cpone/periode-tarif/12?search=Darah&page=2", nil)
|
||||||
|
request.SetPathValue("periodeTarifID", "12")
|
||||||
|
|
||||||
|
Handler{periodeTarifDetailRepo: repo}.ListPeriodeTarifLayanan(recorder, request)
|
||||||
|
|
||||||
|
if recorder.Code != http.StatusOK || repo.id != 12 {
|
||||||
|
t.Fatalf("status=%d id=%d body=%s", recorder.Code, repo.id, recorder.Body.String())
|
||||||
|
}
|
||||||
|
for _, expected := range []string{
|
||||||
|
`"LayananID":"6413"`, `"NamaLayanan":"Darah Lengkap"`,
|
||||||
|
`"LayananDepartemenID":20`, `"Departemen":"LAB"`,
|
||||||
|
`"Kelas":"Default"`, `"Harga":"125000.00"`,
|
||||||
|
} {
|
||||||
|
if !strings.Contains(recorder.Body.String(), expected) {
|
||||||
|
t.Fatalf("response missing %s: %s", expected, recorder.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListPeriodeTarifLayananUsesSearchAndPagination(t *testing.T) {
|
||||||
|
repo := &fakePeriodeTarifDetailRepository{total: 41}
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "http://localhost/api/cpone/periode-tarif/12?search=Admin&page=1", nil)
|
||||||
|
request.SetPathValue("periodeTarifID", "12")
|
||||||
|
|
||||||
|
Handler{periodeTarifDetailRepo: repo}.ListPeriodeTarifLayanan(recorder, request)
|
||||||
|
|
||||||
|
if recorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
if repo.params.Page != 1 || repo.params.PerPage != periodeTarifLayananPerPage || repo.params.Search != "Admin" {
|
||||||
|
t.Fatalf("params=%+v", repo.params)
|
||||||
|
}
|
||||||
|
for _, expected := range []string{
|
||||||
|
`"current_page":1`, `"last_page":3`, `"per_page":20`, `"total":41`,
|
||||||
|
`"next_page_url":"http://localhost/api/cpone/periode-tarif/12?page=2\u0026per_page=20\u0026search=Admin"`,
|
||||||
|
} {
|
||||||
|
if !strings.Contains(recorder.Body.String(), expected) {
|
||||||
|
t.Fatalf("response missing %s: %s", expected, recorder.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListPeriodeTarifLayananValidationAndMissingTable(t *testing.T) {
|
||||||
|
for _, rawID := range []string{"", "abc", "0", "1.5"} {
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/api/cpone/periode-tarif/"+rawID, nil)
|
||||||
|
request.SetPathValue("periodeTarifID", rawID)
|
||||||
|
Handler{periodeTarifDetailRepo: &fakePeriodeTarifDetailRepository{}}.ListPeriodeTarifLayanan(recorder, request)
|
||||||
|
if recorder.Code != http.StatusUnprocessableEntity {
|
||||||
|
t.Fatalf("id=%q status=%d body=%s", rawID, recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/api/cpone/periode-tarif/12", nil)
|
||||||
|
request.SetPathValue("periodeTarifID", "12")
|
||||||
|
Handler{periodeTarifDetailRepo: &fakePeriodeTarifDetailRepository{err: repository.ErrTarifTableNotFound}}.
|
||||||
|
ListPeriodeTarifLayanan(recorder, request)
|
||||||
|
if recorder.Code != http.StatusNotFound || !strings.Contains(recorder.Body.String(), "harga_layanan_12") {
|
||||||
|
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListPeriodeTarifLayananInternalError(t *testing.T) {
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/api/cpone/periode-tarif/12", nil)
|
||||||
|
request.SetPathValue("periodeTarifID", "12")
|
||||||
|
Handler{periodeTarifDetailRepo: &fakePeriodeTarifDetailRepository{err: errors.New("query failed")}}.
|
||||||
|
ListPeriodeTarifLayanan(recorder, request)
|
||||||
|
if recorder.Code != http.StatusInternalServerError {
|
||||||
|
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
72
internal/http/periode_tarif_test.go
Normal file
72
internal/http/periode_tarif_test.go
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
package http
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"primaya-api/cpone-middleware/internal/repository"
|
||||||
|
)
|
||||||
|
|
||||||
|
type fakePeriodeTarifRepository struct {
|
||||||
|
items []repository.PeriodeTarif
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakePeriodeTarifRepository) ListPeriodeTarif(_ context.Context) ([]repository.PeriodeTarif, error) {
|
||||||
|
return f.items, f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *fakePeriodeTarifRepository) ListPeriodeTarifLayanan(_ context.Context, _ repository.PeriodeTarifLayananListParams) (repository.PeriodeTarifLayananPage, error) {
|
||||||
|
return repository.PeriodeTarifLayananPage{}, f.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListPeriodeTarifReturnsAvailablePeriods(t *testing.T) {
|
||||||
|
keterangan := "Tarif Dasar 2026"
|
||||||
|
repo := &fakePeriodeTarifRepository{items: []repository.PeriodeTarif{{
|
||||||
|
PeriodeTarifID: 42, Keterangan: &keterangan,
|
||||||
|
TanggalBerlaku: "2026-01-01", TanggalBerakhir: "2026-12-31",
|
||||||
|
}}}
|
||||||
|
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/api/cpone/periode-tarif", nil)
|
||||||
|
Handler{periodeTarifRepo: repo}.ListPeriodeTarif(recorder, request)
|
||||||
|
|
||||||
|
if recorder.Code != http.StatusOK {
|
||||||
|
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
for _, expected := range []string{
|
||||||
|
`"success":true`, `"message":"ok"`, `"PeriodeTarifID":42`,
|
||||||
|
`"Keterangan":"Tarif Dasar 2026"`, `"TanggalBerlaku":"2026-01-01"`,
|
||||||
|
`"TanggalBerakhir":"2026-12-31"`,
|
||||||
|
} {
|
||||||
|
if !strings.Contains(recorder.Body.String(), expected) {
|
||||||
|
t.Fatalf("response missing %s: %s", expected, recorder.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListPeriodeTarifReturnsEmptyArray(t *testing.T) {
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/api/cpone/periode-tarif", nil)
|
||||||
|
Handler{periodeTarifRepo: &fakePeriodeTarifRepository{items: []repository.PeriodeTarif{}}}.
|
||||||
|
ListPeriodeTarif(recorder, request)
|
||||||
|
|
||||||
|
if recorder.Code != http.StatusOK || !strings.Contains(recorder.Body.String(), `"data":[]`) {
|
||||||
|
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestListPeriodeTarifInternalError(t *testing.T) {
|
||||||
|
recorder := httptest.NewRecorder()
|
||||||
|
request := httptest.NewRequest(http.MethodGet, "/api/cpone/periode-tarif", nil)
|
||||||
|
Handler{periodeTarifRepo: &fakePeriodeTarifRepository{err: errors.New("query failed")}}.
|
||||||
|
ListPeriodeTarif(recorder, request)
|
||||||
|
|
||||||
|
if recorder.Code != http.StatusInternalServerError {
|
||||||
|
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
176
internal/repository/periode_tarif.go
Normal file
176
internal/repository/periode_tarif.go
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PeriodeTarif struct {
|
||||||
|
PeriodeTarifID int64 `json:"PeriodeTarifID"`
|
||||||
|
Keterangan *string `json:"Keterangan"`
|
||||||
|
TanggalBerlaku string `json:"TanggalBerlaku"`
|
||||||
|
TanggalBerakhir string `json:"TanggalBerakhir"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r MySQLLayananRepository) ListPeriodeTarif(ctx context.Context) ([]PeriodeTarif, error) {
|
||||||
|
rows, err := r.db.QueryContext(ctx, `
|
||||||
|
SELECT PeriodeTarifID, Keterangan,
|
||||||
|
CAST(TanggalBerlaku AS CHAR), CAST(TanggalBerakhir AS CHAR)
|
||||||
|
FROM periode_tarif
|
||||||
|
WHERE NA = 'N'
|
||||||
|
ORDER BY Keterangan ASC, PeriodeTarifID ASC
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
result := make([]PeriodeTarif, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var item PeriodeTarif
|
||||||
|
var keterangan sql.NullString
|
||||||
|
if err := rows.Scan(&item.PeriodeTarifID, &keterangan,
|
||||||
|
&item.TanggalBerlaku, &item.TanggalBerakhir); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
item.Keterangan = nullStringPtr(keterangan)
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type PeriodeTarifLayanan struct {
|
||||||
|
TarifLayananID int64 `json:"TarifLayananID"`
|
||||||
|
LayananID *string `json:"LayananID"`
|
||||||
|
NamaLayanan *string `json:"NamaLayanan"`
|
||||||
|
LayananDepartemenID int64 `json:"LayananDepartemenID"`
|
||||||
|
Departemen *string `json:"Departemen"`
|
||||||
|
Kelas string `json:"Kelas"`
|
||||||
|
NamaKelas *string `json:"NamaKelas"`
|
||||||
|
Harga *string `json:"Harga"`
|
||||||
|
TipeShare *string `json:"TipeShare"`
|
||||||
|
ShareDokter *string `json:"ShareDokter"`
|
||||||
|
ShareRS *string `json:"ShareRS"`
|
||||||
|
ShareExternal *string `json:"ShareExternal"`
|
||||||
|
ShareLain *string `json:"ShareLain"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PeriodeTarifLayananListParams struct {
|
||||||
|
PeriodeTarifID int64
|
||||||
|
Page int
|
||||||
|
PerPage int
|
||||||
|
Search string
|
||||||
|
}
|
||||||
|
|
||||||
|
type PeriodeTarifLayananPage struct {
|
||||||
|
Items []PeriodeTarifLayanan
|
||||||
|
Total int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r MySQLLayananRepository) ListPeriodeTarifLayanan(ctx context.Context, params PeriodeTarifLayananListParams) (PeriodeTarifLayananPage, error) {
|
||||||
|
tableName := "harga_layanan_" + strconv.FormatInt(params.PeriodeTarifID, 10)
|
||||||
|
exists, err := r.tableExists(ctx, tableName)
|
||||||
|
if err != nil {
|
||||||
|
return PeriodeTarifLayananPage{}, err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return PeriodeTarifLayananPage{}, ErrTarifTableNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
where := `
|
||||||
|
WHERE tarif.NA = 'N'
|
||||||
|
AND layanan_dept.NA = 'N'`
|
||||||
|
args := make([]any, 0, 4)
|
||||||
|
if params.Search != "" {
|
||||||
|
where += `
|
||||||
|
AND (layanan_dept.LayananID LIKE ? OR layanan.Nama LIKE ?)`
|
||||||
|
like := "%" + params.Search + "%"
|
||||||
|
args = append(args, like, like)
|
||||||
|
}
|
||||||
|
|
||||||
|
countQuery := fmt.Sprintf(`
|
||||||
|
SELECT COUNT(*)
|
||||||
|
FROM %s AS tarif
|
||||||
|
INNER JOIN layanandepartemen AS layanan_dept
|
||||||
|
ON layanan_dept.LayananDepartemenID = tarif.LayananDepartemenID
|
||||||
|
LEFT JOIN masterlayanan AS layanan
|
||||||
|
ON layanan.LayananID = layanan_dept.LayananID
|
||||||
|
%s
|
||||||
|
`, tableName, where)
|
||||||
|
var total int
|
||||||
|
if err := r.db.QueryRowContext(ctx, countQuery, args...).Scan(&total); err != nil {
|
||||||
|
return PeriodeTarifLayananPage{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
query := fmt.Sprintf(`
|
||||||
|
SELECT
|
||||||
|
tarif.HargaLayananID,
|
||||||
|
layanan_dept.LayananID,
|
||||||
|
layanan.Nama,
|
||||||
|
tarif.LayananDepartemenID,
|
||||||
|
departemen.Nama,
|
||||||
|
tarif.KelasID,
|
||||||
|
kelas.Nama,
|
||||||
|
CAST(tarif.Harga AS CHAR),
|
||||||
|
tarif.ShareTipe,
|
||||||
|
CAST(tarif.ShareDokter AS CHAR),
|
||||||
|
CAST(tarif.ShareRS AS CHAR),
|
||||||
|
CAST(tarif.ShareExternal AS CHAR),
|
||||||
|
CAST(tarif.ShareLain AS CHAR)
|
||||||
|
FROM %s AS tarif
|
||||||
|
INNER JOIN layanandepartemen AS layanan_dept
|
||||||
|
ON layanan_dept.LayananDepartemenID = tarif.LayananDepartemenID
|
||||||
|
LEFT JOIN masterlayanan AS layanan
|
||||||
|
ON layanan.LayananID = layanan_dept.LayananID
|
||||||
|
LEFT JOIN departemen AS departemen
|
||||||
|
ON departemen.DepartemenID = layanan_dept.DepartemenID
|
||||||
|
LEFT JOIN kelas AS kelas
|
||||||
|
ON kelas.KelasID = tarif.KelasID
|
||||||
|
%s
|
||||||
|
ORDER BY layanan.Nama, layanan_dept.LayananID, tarif.KelasID, tarif.HargaLayananID
|
||||||
|
LIMIT ? OFFSET ?
|
||||||
|
`, tableName, where)
|
||||||
|
args = append(args, params.PerPage, (params.Page-1)*params.PerPage)
|
||||||
|
|
||||||
|
rows, err := r.db.QueryContext(ctx, query, args...)
|
||||||
|
if err != nil {
|
||||||
|
return PeriodeTarifLayananPage{}, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
|
||||||
|
result := make([]PeriodeTarifLayanan, 0)
|
||||||
|
for rows.Next() {
|
||||||
|
var item PeriodeTarifLayanan
|
||||||
|
var layananID, namaLayanan, departemen, namaKelas, harga sql.NullString
|
||||||
|
var tipeShare, shareDokter, shareRS, shareExternal, shareLain sql.NullString
|
||||||
|
if err := rows.Scan(
|
||||||
|
&item.TarifLayananID, &layananID, &namaLayanan,
|
||||||
|
&item.LayananDepartemenID, &departemen, &item.Kelas, &namaKelas,
|
||||||
|
&harga, &tipeShare, &shareDokter, &shareRS, &shareExternal, &shareLain,
|
||||||
|
); err != nil {
|
||||||
|
return PeriodeTarifLayananPage{}, err
|
||||||
|
}
|
||||||
|
item.LayananID = nullStringPtr(layananID)
|
||||||
|
item.NamaLayanan = nullStringPtr(namaLayanan)
|
||||||
|
item.Departemen = nullStringPtr(departemen)
|
||||||
|
item.NamaKelas = nullStringPtr(namaKelas)
|
||||||
|
item.Harga = nullStringPtr(harga)
|
||||||
|
item.TipeShare = nullStringPtr(tipeShare)
|
||||||
|
item.ShareDokter = nullStringPtr(shareDokter)
|
||||||
|
item.ShareRS = nullStringPtr(shareRS)
|
||||||
|
item.ShareExternal = nullStringPtr(shareExternal)
|
||||||
|
item.ShareLain = nullStringPtr(shareLain)
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
if err := rows.Err(); err != nil {
|
||||||
|
return PeriodeTarifLayananPage{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return PeriodeTarifLayananPage{Items: result, Total: total}, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user