diff --git a/README.md b/README.md index 8fb4bf0..befb12f 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,21 @@ Authorization: Bearer GET /api/cpone/tarif-kerjasama/{id}?group_tarif_id=&active=N&effective_date=2026-08-22 Authorization: Bearer +GET /api/cpone/periode-tarif +Authorization: Bearer +X-RS-Code: + +Endpoint ini menampilkan seluruh periode tarif dari HIS dengan `NA = 'N'`. + +GET /api/cpone/periode-tarif/{PeriodeTarifID}?search=&page=1 +Authorization: Bearer +X-RS-Code: + +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 Authorization: Bearer diff --git a/cmd/server/main.go b/cmd/server/main.go index c2c1e1f..db078d5 100644 --- a/cmd/server/main.go +++ b/cmd/server/main.go @@ -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/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/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("POST /api/cpone/patients/medrec", protected(http.HandlerFunc(handler.StoreMedrec))) mux.Handle("POST /api/cpone/patients/lab-registration", protected(http.HandlerFunc(handler.RegisterLab))) diff --git a/internal/http/handler.go b/internal/http/handler.go index 3a95d12..ec56ec6 100644 --- a/internal/http/handler.go +++ b/internal/http/handler.go @@ -41,6 +41,14 @@ type TarifKerjasamaRepository interface { 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 { ListPerusahaan(ctx context.Context, params repository.PerusahaanListParams) (repository.PerusahaanPage, error) GetPerusahaan(ctx context.Context, id string) (*repository.PerusahaanDetail, error) @@ -48,13 +56,15 @@ type PerusahaanRepository interface { } type Handler struct { - layananRepo LayananRepository - labRepo LabRegistrationRepository - patientRepo PatientRepository - labResultRepo LabResultRepository - paketRepo PaketRepository - tarifRepo TarifKerjasamaRepository - perusahaanRepo PerusahaanRepository + layananRepo LayananRepository + labRepo LabRegistrationRepository + patientRepo PatientRepository + labResultRepo LabResultRepository + paketRepo PaketRepository + tarifRepo TarifKerjasamaRepository + periodeTarifRepo PeriodeTarifRepository + periodeTarifDetailRepo PeriodeTarifDetailRepository + perusahaanRepo PerusahaanRepository } type CPONERepository interface { @@ -64,13 +74,16 @@ type CPONERepository interface { LabResultRepository PaketRepository TarifKerjasamaRepository + PeriodeTarifRepository + PeriodeTarifDetailRepository PerusahaanRepository } func NewHandler(repo CPONERepository) Handler { return Handler{ 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 } +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 { if repo, ok := selectedRepository(ctx); ok { return repo diff --git a/internal/http/periode_tarif.go b/internal/http/periode_tarif.go new file mode 100644 index 0000000..2f0cceb --- /dev/null +++ b/internal/http/periode_tarif.go @@ -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, + }) +} diff --git a/internal/http/periode_tarif_detail.go b/internal/http/periode_tarif_detail.go new file mode 100644 index 0000000..0a20be5 --- /dev/null +++ b/internal/http/periode_tarif_detail.go @@ -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() +} diff --git a/internal/http/periode_tarif_detail_test.go b/internal/http/periode_tarif_detail_test.go new file mode 100644 index 0000000..422140e --- /dev/null +++ b/internal/http/periode_tarif_detail_test.go @@ -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()) + } +} diff --git a/internal/http/periode_tarif_test.go b/internal/http/periode_tarif_test.go new file mode 100644 index 0000000..5469f4a --- /dev/null +++ b/internal/http/periode_tarif_test.go @@ -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()) + } +} diff --git a/internal/repository/periode_tarif.go b/internal/repository/periode_tarif.go new file mode 100644 index 0000000..f78b0eb --- /dev/null +++ b/internal/repository/periode_tarif.go @@ -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 +}