package http import ( "encoding/json" "errors" "fmt" "math" "net/http" "strconv" "strings" "unicode/utf8" "primaya-api/cpone-middleware/internal/repository" ) func (h Handler) AddLabServices(w http.ResponseWriter, r *http.Request) { payload, _ := decodeJSONObject(r) params, validationMessage := validateAddLabServices(payload) if validationMessage != "" { writeLabResponse(w, http.StatusUnprocessableEntity, false, "validation_error", validationMessage, nil) return } outcome, err := h.labRepository(r.Context()).AddLabServices(r.Context(), r.PathValue("regId"), params) if err != nil { if validationErr, ok := asValidationError(err); ok { writeLabResponse(w, http.StatusUnprocessableEntity, false, "validation_error", validationErr, nil) return } writeLabResponse(w, http.StatusInternalServerError, false, "error", err.Error(), nil) return } writeLabResponse(w, outcome.HTTPStatus, outcome.Success, outcome.Status, outcome.Message, outcome.Data) } func validateAddLabServices(payload map[string]any) (repository.AddLabServicesParams, string) { value, exists := payload["layanan"] if !exists || isLaravelEmpty(value) { return repository.AddLabServicesParams{}, "Minimal satu layanan wajib diisi" } items, ok := value.([]any) if !ok { return repository.AddLabServicesParams{}, "The layanan must be an array." } if len(items) == 0 { return repository.AddLabServicesParams{}, "Minimal satu layanan wajib diisi" } services := make([]repository.RequestedLabService, len(items)) seenIDs := make(map[string]struct{}, len(items)) for index, rawItem := range items { item, _ := rawItem.(map[string]any) serviceID, message := requiredNestedString(item, "layanan_id", index, 50, "Layanan ID wajib diisi") if message != "" { return repository.AddLabServicesParams{}, message } if _, duplicate := seenIDs[serviceID]; duplicate { return repository.AddLabServicesParams{}, "Layanan yang sama tidak boleh dikirim lebih dari satu kali" } seenIDs[serviceID] = struct{}{} services[index].LayananID = serviceID } for index, rawItem := range items { item, _ := rawItem.(map[string]any) services[index].Jumlah = 1 if value, present := nullableValue(item, "jumlah"); present { quantity, valid := parseInteger(value) if !valid { return repository.AddLabServicesParams{}, nestedMessage(index, "jumlah", "must be an integer") } if quantity < 1 { return repository.AddLabServicesParams{}, nestedMessage(index, "jumlah", "must be at least 1") } if quantity > 100 { return repository.AddLabServicesParams{}, nestedMessage(index, "jumlah", "must not be greater than 100") } services[index].Jumlah = int(quantity) } } for index, rawItem := range items { item, _ := rawItem.(map[string]any) if value, present := nullableValue(item, "cito"); present { cito, valid := parseLaravelBoolean(value) if !valid { return repository.AddLabServicesParams{}, nestedMessage(index, "cito", "field must be true or false") } services[index].Cito = cito } } for index, rawItem := range items { item, _ := rawItem.(map[string]any) if value, present := nullableValue(item, "markup_cito"); present { markup, valid := parseNumeric(value) if !valid { return repository.AddLabServicesParams{}, nestedMessage(index, "markup_cito", "must be a number") } if markup < 0 { return repository.AddLabServicesParams{}, nestedMessage(index, "markup_cito", "must be at least 0") } if markup > 100 { return repository.AddLabServicesParams{}, nestedMessage(index, "markup_cito", "must not be greater than 100") } services[index].MarkupCito = markup } if !services[index].Cito { services[index].MarkupCito = 0 } } for index, rawItem := range items { item, _ := rawItem.(map[string]any) notes, _, message := optionalNestedString(item, "catatan", index, 500) if message != "" { return repository.AddLabServicesParams{}, message } services[index].Catatan = notes } channel, channelPresent, message := optionalString(payload, "channel", 50) if message != "" { return repository.AddLabServicesParams{}, message } if !channelPresent { channel = "CPONE" } channel = strings.ToUpper(strings.TrimSpace(channel)) return repository.AddLabServicesParams{Layanan: services, Channel: channel}, "" } func requiredNestedString(item map[string]any, key string, index int, maxLength int, requiredMessage string) (string, string) { value, exists := item[key] if !exists || isLaravelEmpty(value) { return "", requiredMessage } text, ok := value.(string) if !ok { return "", nestedMessage(index, key, "must be a string") } if utf8.RuneCountInString(text) > maxLength { return "", nestedMessage(index, key, fmt.Sprintf("must not be greater than %d characters", maxLength)) } return text, "" } func optionalNestedString(item map[string]any, key string, index int, maxLength int) (string, bool, string) { value, present := nullableValue(item, key) if !present { return "", false, "" } text, ok := value.(string) if !ok { return "", false, nestedMessage(index, key, "must be a string") } if utf8.RuneCountInString(text) > maxLength { return "", false, nestedMessage(index, key, fmt.Sprintf("must not be greater than %d characters", maxLength)) } return text, true, "" } func nestedMessage(index int, key string, ruleMessage string) string { attribute := fmt.Sprintf("layanan.%d.%s", index, strings.ReplaceAll(key, "_", " ")) if strings.HasPrefix(ruleMessage, "field ") { return fmt.Sprintf("The %s %s.", attribute, ruleMessage) } return fmt.Sprintf("The %s %s.", attribute, ruleMessage) } func parseLaravelBoolean(value any) (bool, bool) { switch typed := value.(type) { case bool: return typed, true case json.Number: if typed.String() == "1" { return true, true } if typed.String() == "0" { return false, true } case string: if typed == "1" { return true, true } if typed == "0" { return false, true } } return false, false } func parseNumeric(value any) (float64, bool) { var text string switch typed := value.(type) { case json.Number: text = typed.String() case string: text = strings.TrimSpace(typed) default: return 0, false } parsed, err := strconv.ParseFloat(text, 64) return parsed, err == nil && !math.IsInf(parsed, 0) && !math.IsNaN(parsed) } func asValidationError(err error) (string, bool) { var validationErr repository.ValidationError if errors.As(err, &validationErr) { return validationErr.Message, true } return "", false }