Files
cpone_midleware/internal/http/lab_services.go

216 lines
6.4 KiB
Go

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
}