package http import ( "context" "errors" "net/http" "regexp" "strings" "primaya-api/cpone-middleware/internal/repository" ) var digitsOnly = regexp.MustCompile(`^\d+$`) type LayananRepository interface { ListLayanan(ctx context.Context, params repository.LayananListParams) (repository.LayananPage, error) MappingHarga(ctx context.Context, params repository.MappingHargaParams) ([]repository.TarifLayanan, error) } type LabRegistrationRepository interface { RegisterLab(ctx context.Context, params repository.LabRegistrationParams) (repository.LabRegistrationOutcome, error) AddLabServices(ctx context.Context, regID string, params repository.AddLabServicesParams) (repository.LabRegistrationOutcome, error) AddLabPackage(ctx context.Context, regID string, params repository.AddLabPackageParams) (repository.LabRegistrationOutcome, error) } type PatientRepository interface { StoreMedrec(ctx context.Context, params repository.MedrecParams) (repository.MedrecOutcome, error) } type RegistrationRepository interface { RegisterPatient(ctx context.Context, params repository.RegistrationParams) (repository.RegistrationOutcome, error) } type LabResultRepository interface { GetLabResult(ctx context.Context, labNumber string) (repository.LabResultOutcome, error) } type PaketRepository interface { ListPaket(ctx context.Context, params repository.PaketListParams) (repository.PaketPage, error) GetPaket(ctx context.Context, id string) (*repository.PaketDetail, error) CreatePaket(ctx context.Context, params repository.CreatePaketParams) (repository.CreatePaketResult, error) } type TarifKerjasamaRepository interface { ListTarifKerjasama(ctx context.Context, params repository.TarifKerjasamaListParams) (repository.TarifKerjasamaPage, 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 { ListPerusahaan(ctx context.Context, params repository.PerusahaanListParams) (repository.PerusahaanPage, error) GetPerusahaan(ctx context.Context, id string) (*repository.PerusahaanDetail, error) GetPerusahaanGroupTarif(ctx context.Context, id, groupTarifID string) (*repository.PerusahaanGroupTarifDetail, error) } 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 patientRepo PatientRepository registrationRepo RegistrationRepository labResultRepo LabResultRepository paketRepo PaketRepository tarifRepo TarifKerjasamaRepository periodeTarifRepo PeriodeTarifRepository periodeTarifDetailRepo PeriodeTarifDetailRepository perusahaanRepo PerusahaanRepository dokterRepo DokterRepository laporanMCURepo LaporanMCURepository hospitalCode string departmentID string } type CPONERepository interface { LayananRepository LabRegistrationRepository PatientRepository RegistrationRepository LabResultRepository PaketRepository TarifKerjasamaRepository PeriodeTarifRepository PeriodeTarifDetailRepository PerusahaanRepository DokterRepository } func NewHandler(repo CPONERepository) 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 { return Handler{ hospitalCode: strings.ToUpper(strings.TrimSpace(hospitalCode)), departmentID: strings.TrimSpace(departmentID), } } func (h Handler) layananRepository(ctx context.Context) LayananRepository { if repo, ok := selectedRepository(ctx); ok { return repo } return h.layananRepo } func (h Handler) labRepository(ctx context.Context) LabRegistrationRepository { if repo, ok := selectedRepository(ctx); ok { return repo } return h.labRepo } func (h Handler) patientRepository(ctx context.Context) PatientRepository { if repo, ok := selectedRepository(ctx); ok { return repo } return h.patientRepo } func (h Handler) registrationRepository(ctx context.Context) RegistrationRepository { if repo, ok := selectedRepository(ctx); ok { return repo } return h.registrationRepo } func (h Handler) labResultRepository(ctx context.Context) LabResultRepository { if repo, ok := selectedRepository(ctx); ok { return repo } return h.labResultRepo } func (h Handler) paketRepository(ctx context.Context) PaketRepository { if repo, ok := selectedRepository(ctx); ok { return repo } return h.paketRepo } func (h Handler) tarifRepository(ctx context.Context) TarifKerjasamaRepository { if repo, ok := selectedRepository(ctx); ok { return repo } 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 } return h.perusahaanRepo } func (h Handler) dokterRepository(ctx context.Context) DokterRepository { if repo, ok := selectedRepository(ctx); ok { return repo } 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 { 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.layananRepository(r.Context()).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 }