Add MCU report endpoint and deployment tooling
This commit is contained in:
@@ -64,6 +64,10 @@ type DokterRepository interface {
|
||||
ListDokter(ctx context.Context, params repository.DokterListParams) (repository.DokterPage, error)
|
||||
}
|
||||
|
||||
type LaporanMCURepository interface {
|
||||
ListLaporanMCU(ctx context.Context, params repository.LaporanMCUParams) ([]repository.LaporanMCURow, error)
|
||||
}
|
||||
|
||||
type Handler struct {
|
||||
layananRepo LayananRepository
|
||||
labRepo LabRegistrationRepository
|
||||
@@ -76,6 +80,7 @@ type Handler struct {
|
||||
periodeTarifDetailRepo PeriodeTarifDetailRepository
|
||||
perusahaanRepo PerusahaanRepository
|
||||
dokterRepo DokterRepository
|
||||
laporanMCURepo LaporanMCURepository
|
||||
hospitalCode string
|
||||
departmentID string
|
||||
}
|
||||
@@ -95,12 +100,19 @@ type CPONERepository interface {
|
||||
}
|
||||
|
||||
func NewHandler(repo CPONERepository) Handler {
|
||||
return Handler{
|
||||
handler := Handler{
|
||||
layananRepo: repo, labRepo: repo, patientRepo: repo, registrationRepo: repo,
|
||||
labResultRepo: repo, paketRepo: repo, tarifRepo: repo,
|
||||
periodeTarifRepo: repo, periodeTarifDetailRepo: repo, perusahaanRepo: repo,
|
||||
dokterRepo: repo,
|
||||
}
|
||||
// Keep NewHandler source-compatible with repositories created before the
|
||||
// MCU report was added. The concrete MySQL repository implements this
|
||||
// optional capability and the dynamic production handler uses it.
|
||||
if reportRepo, ok := any(repo).(LaporanMCURepository); ok {
|
||||
handler.laporanMCURepo = reportRepo
|
||||
}
|
||||
return handler
|
||||
}
|
||||
|
||||
func NewDynamicHandler(hospitalCode string, departmentID string) Handler {
|
||||
@@ -187,6 +199,13 @@ func (h Handler) dokterRepository(ctx context.Context) DokterRepository {
|
||||
return h.dokterRepo
|
||||
}
|
||||
|
||||
func (h Handler) laporanMCURepository(ctx context.Context) LaporanMCURepository {
|
||||
if repo, ok := selectedRepository(ctx); ok {
|
||||
return repo
|
||||
}
|
||||
return h.laporanMCURepo
|
||||
}
|
||||
|
||||
func (h Handler) MappingHarga(w http.ResponseWriter, r *http.Request) {
|
||||
layananIDs := parseLayananIDs(r)
|
||||
if len(layananIDs) == 0 {
|
||||
|
||||
72
internal/http/laporan_mcu.go
Normal file
72
internal/http/laporan_mcu.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"primaya-api/cpone-middleware/internal/repository"
|
||||
)
|
||||
|
||||
// ListLaporanMCU exposes the data formerly produced as mcu.xls by
|
||||
// docs/lap_mcu.php. The API keeps the same lower-case date parameters as the
|
||||
// PHP script and returns the spreadsheet rows as JSON data.
|
||||
func (h Handler) ListLaporanMCU(w http.ResponseWriter, r *http.Request) {
|
||||
tanggal, ok := firstQueryValue(r, "tanggal", "Tanggal")
|
||||
if !ok || strings.TrimSpace(tanggal) == "" {
|
||||
WriteJSON(w, http.StatusUnprocessableEntity, Response{
|
||||
Success: false, Message: "Parameter tanggal wajib diisi", Data: nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
tanggal2, ok := firstQueryValue(r, "tanggal2", "Tanggal2")
|
||||
if !ok || strings.TrimSpace(tanggal2) == "" {
|
||||
WriteJSON(w, http.StatusUnprocessableEntity, Response{
|
||||
Success: false, Message: "Parameter tanggal2 wajib diisi", Data: nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
start, err := parseReportDate(tanggal)
|
||||
if err != nil {
|
||||
WriteJSON(w, http.StatusUnprocessableEntity, Response{
|
||||
Success: false, Message: "Parameter tanggal harus format Y-m-d", Data: nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
end, err := parseReportDate(tanggal2)
|
||||
if err != nil {
|
||||
WriteJSON(w, http.StatusUnprocessableEntity, Response{
|
||||
Success: false, Message: "Parameter tanggal2 harus format Y-m-d", Data: nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
if end.Before(start) {
|
||||
WriteJSON(w, http.StatusUnprocessableEntity, Response{
|
||||
Success: false, Message: "Parameter tanggal2 tidak boleh lebih kecil dari tanggal", Data: nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
rows, err := h.laporanMCURepository(r.Context()).ListLaporanMCU(r.Context(), repository.LaporanMCUParams{
|
||||
Tanggal: start.Format("2006-01-02"), Tanggal2: end.Format("2006-01-02"),
|
||||
})
|
||||
if err != nil {
|
||||
WriteJSON(w, http.StatusInternalServerError, Response{
|
||||
Success: false, Message: "internal server error", Data: nil,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
WriteJSON(w, http.StatusOK, Response{Success: true, Message: "ok", Data: rows})
|
||||
}
|
||||
|
||||
// GetLaporanMCU is an explicit alias for callers that use the Get naming
|
||||
// convention for report endpoints.
|
||||
func (h Handler) GetLaporanMCU(w http.ResponseWriter, r *http.Request) {
|
||||
h.ListLaporanMCU(w, r)
|
||||
}
|
||||
|
||||
func parseReportDate(value string) (time.Time, error) {
|
||||
return time.Parse("2006-01-02", strings.TrimSpace(value))
|
||||
}
|
||||
74
internal/http/laporan_mcu_test.go
Normal file
74
internal/http/laporan_mcu_test.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"primaya-api/cpone-middleware/internal/repository"
|
||||
)
|
||||
|
||||
type fakeLaporanMCURepository struct {
|
||||
rows []repository.LaporanMCURow
|
||||
err error
|
||||
params repository.LaporanMCUParams
|
||||
}
|
||||
|
||||
func (f *fakeLaporanMCURepository) ListLaporanMCU(_ context.Context, params repository.LaporanMCUParams) ([]repository.LaporanMCURow, error) {
|
||||
f.params = params
|
||||
return f.rows, f.err
|
||||
}
|
||||
|
||||
func TestListLaporanMCUResponseAndDateRange(t *testing.T) {
|
||||
regID := "MCU260900001"
|
||||
repo := &fakeLaporanMCURepository{rows: []repository.LaporanMCURow{{No: 1, RegID: ®ID}}}
|
||||
handler := Handler{laporanMCURepo: repo}
|
||||
recorder := httptest.NewRecorder()
|
||||
request := httptest.NewRequest(http.MethodGet, "/api/cpone/reports/mcu?tanggal=2026-09-01&tanggal2=2026-09-07", nil)
|
||||
|
||||
handler.ListLaporanMCU(recorder, request)
|
||||
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
if repo.params.Tanggal != "2026-09-01" || repo.params.Tanggal2 != "2026-09-07" {
|
||||
t.Fatalf("params=%+v", repo.params)
|
||||
}
|
||||
for _, expected := range []string{`"success":true`, `"message":"ok"`, `"No":1`, `"RegID":"MCU260900001"`} {
|
||||
if !strings.Contains(recorder.Body.String(), expected) {
|
||||
t.Fatalf("response missing %s: %s", expected, recorder.Body.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestListLaporanMCUValidatesDateRange(t *testing.T) {
|
||||
repo := &fakeLaporanMCURepository{}
|
||||
handler := Handler{laporanMCURepo: repo}
|
||||
for target := range map[string]struct{}{
|
||||
"/api/cpone/reports/mcu": {},
|
||||
"/api/cpone/reports/mcu?tanggal=01-09-2026&tanggal2=2026-09-07": {},
|
||||
"/api/cpone/reports/mcu?tanggal=2026-09-08&tanggal2=2026-09-07": {},
|
||||
"/api/cpone/reports/mcu?tanggal=2026-09-01&tanggal2=not-a-date": {},
|
||||
} {
|
||||
recorder := httptest.NewRecorder()
|
||||
handler.ListLaporanMCU(recorder, httptest.NewRequest(http.MethodGet, target, nil))
|
||||
if recorder.Code != http.StatusUnprocessableEntity {
|
||||
t.Fatalf("target=%s status=%d body=%s", target, recorder.Code, recorder.Body.String())
|
||||
}
|
||||
}
|
||||
if repo.params != (repository.LaporanMCUParams{}) {
|
||||
t.Fatalf("repository should not be called for invalid dates: %+v", repo.params)
|
||||
}
|
||||
}
|
||||
|
||||
func TestListLaporanMCUInternalError(t *testing.T) {
|
||||
handler := Handler{laporanMCURepo: &fakeLaporanMCURepository{err: errors.New("database unavailable")}}
|
||||
recorder := httptest.NewRecorder()
|
||||
handler.ListLaporanMCU(recorder, httptest.NewRequest(http.MethodGet, "/api/cpone/reports/mcu?tanggal=2026-09-01&tanggal2=2026-09-01", nil))
|
||||
if recorder.Code != http.StatusInternalServerError {
|
||||
t.Fatalf("status=%d body=%s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user