332 lines
9.5 KiB
Go
332 lines
9.5 KiB
Go
package http
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"math"
|
|
"net/http"
|
|
"regexp"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
"unicode/utf8"
|
|
|
|
"primaya-api/cpone-middleware/internal/repository"
|
|
)
|
|
|
|
var integerValue = regexp.MustCompile(`^[+-]?\d+$`)
|
|
|
|
type LabRegistrationResponse struct {
|
|
Success bool `json:"success"`
|
|
Status string `json:"status"`
|
|
Message string `json:"message"`
|
|
Data *repository.LabRegistrationData `json:"data"`
|
|
}
|
|
|
|
type validatedLabRegistration struct {
|
|
Params repository.LabRegistrationParams
|
|
}
|
|
|
|
func (h Handler) RegisterLab(w http.ResponseWriter, r *http.Request) {
|
|
payload, err := decodeJSONObject(r)
|
|
if err != nil {
|
|
writeLabResponse(w, http.StatusUnprocessableEntity, false, "validation_error", err.Error(), nil)
|
|
return
|
|
}
|
|
|
|
validated, validationMessage := validateLabRegistration(payload)
|
|
if validationMessage != "" {
|
|
writeLabResponse(w, http.StatusUnprocessableEntity, false, "validation_error", validationMessage, nil)
|
|
return
|
|
}
|
|
|
|
outcome, err := h.labRepo.RegisterLab(r.Context(), validated.Params)
|
|
if err != nil {
|
|
var validationErr repository.ValidationError
|
|
if errors.As(err, &validationErr) {
|
|
writeLabResponse(w, http.StatusUnprocessableEntity, false, "validation_error", validationErr.Message, 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 decodeJSONObject(r *http.Request) (map[string]any, error) {
|
|
decoder := json.NewDecoder(r.Body)
|
|
decoder.UseNumber()
|
|
|
|
payload := make(map[string]any)
|
|
if err := decoder.Decode(&payload); err != nil {
|
|
// Laravel treats an empty, malformed, or non-object JSON body as empty
|
|
// request input; validation then reports the first required field.
|
|
return payload, nil
|
|
}
|
|
|
|
var trailing any
|
|
if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) {
|
|
return make(map[string]any), nil
|
|
}
|
|
return payload, nil
|
|
}
|
|
|
|
func validateLabRegistration(payload map[string]any) (validatedLabRegistration, string) {
|
|
medrecID, message := requiredString(payload, "medrec_id", 50, "Medrec ID wajib diisi")
|
|
if message != "" {
|
|
return validatedLabRegistration{}, message
|
|
}
|
|
|
|
jakarta, err := time.LoadLocation("Asia/Jakarta")
|
|
if err != nil {
|
|
return validatedLabRegistration{}, err.Error()
|
|
}
|
|
registrationDate := time.Now().In(jakarta)
|
|
if value, present := nullableValue(payload, "tanggal"); present {
|
|
dateText, ok := value.(string)
|
|
if !ok || !validDateFormat(dateText, jakarta) {
|
|
return validatedLabRegistration{}, "Tanggal registrasi harus format Y-m-d"
|
|
}
|
|
registrationDate, _ = time.ParseInLocation("2006-01-02", dateText, jakarta)
|
|
}
|
|
|
|
departmentID, message := requiredString(payload, "departemen_id", 50, "Departemen wajib diisi untuk membuat registrasi")
|
|
if message != "" {
|
|
return validatedLabRegistration{}, message
|
|
}
|
|
doctorID, message := requiredString(payload, "dokter_id", 50, "Dokter penanggung jawab wajib diisi")
|
|
if message != "" {
|
|
return validatedLabRegistration{}, message
|
|
}
|
|
|
|
senderDoctorID, senderPresent, message := optionalString(payload, "dokter_pengirim_id", 50)
|
|
if message != "" {
|
|
return validatedLabRegistration{}, message
|
|
}
|
|
referrer, referrerPresent, message := optionalString(payload, "perujuk", 255)
|
|
if message != "" {
|
|
return validatedLabRegistration{}, message
|
|
}
|
|
if !senderPresent && !referrerPresent {
|
|
return validatedLabRegistration{}, "Dokter pengirim atau perujuk wajib diisi"
|
|
}
|
|
|
|
patientType, patientTypePresent, message := optionalString(payload, "jenis_pasien_id", 0)
|
|
if message != "" {
|
|
return validatedLabRegistration{}, message
|
|
}
|
|
if patientTypePresent && patientType != "0" {
|
|
return validatedLabRegistration{}, "Registrasi laboratorium API saat ini hanya mendukung pasien umum"
|
|
}
|
|
|
|
tariffPeriodID, message := requiredInteger(payload, "periode_tarif_id", "Periode tarif wajib diisi")
|
|
if message != "" {
|
|
return validatedLabRegistration{}, message
|
|
}
|
|
if tariffPeriodID < 1 {
|
|
return validatedLabRegistration{}, "The periode tarif id must be at least 1."
|
|
}
|
|
|
|
classID, classPresent, message := optionalString(payload, "kelas_id", 50)
|
|
if message != "" {
|
|
return validatedLabRegistration{}, message
|
|
}
|
|
if !classPresent || strings.TrimSpace(classID) == "" {
|
|
classID = "Default"
|
|
} else {
|
|
classID = strings.TrimSpace(classID)
|
|
}
|
|
|
|
var dailyShiftID *int64
|
|
if value, present := nullableValue(payload, "shift_harian_id"); present {
|
|
parsed, ok := parseInteger(value)
|
|
if !ok {
|
|
return validatedLabRegistration{}, "The shift harian id must be an integer."
|
|
}
|
|
if parsed < 1 {
|
|
return validatedLabRegistration{}, "The shift harian id must be at least 1."
|
|
}
|
|
dailyShiftID = &parsed
|
|
}
|
|
|
|
workingDiagnosis, message := requiredString(payload, "diagnosa_kerja", 1000, "Diagnosa kerja wajib diisi")
|
|
if message != "" {
|
|
return validatedLabRegistration{}, message
|
|
}
|
|
if utf8.RuneCountInString(workingDiagnosis) < 3 {
|
|
return validatedLabRegistration{}, "Diagnosa kerja minimal 3 karakter"
|
|
}
|
|
if consistsOfOneRepeatedRune(workingDiagnosis) {
|
|
return validatedLabRegistration{}, "Diagnosa kerja tidak valid"
|
|
}
|
|
|
|
notes, _, message := optionalString(payload, "catatan", 1000)
|
|
if message != "" {
|
|
return validatedLabRegistration{}, message
|
|
}
|
|
channel, channelPresent, message := optionalString(payload, "channel", 50)
|
|
if message != "" {
|
|
return validatedLabRegistration{}, message
|
|
}
|
|
if !channelPresent {
|
|
channel = "CPONE"
|
|
}
|
|
channel = strings.ToUpper(strings.TrimSpace(channel))
|
|
|
|
return validatedLabRegistration{Params: repository.LabRegistrationParams{
|
|
MedrecID: medrecID,
|
|
RegistrationDate: registrationDate,
|
|
DepartmentID: departmentID,
|
|
DoctorID: doctorID,
|
|
SenderDoctorID: senderDoctorID,
|
|
Referrer: referrer,
|
|
TariffPeriodID: tariffPeriodID,
|
|
ClassID: classID,
|
|
DailyShiftID: dailyShiftID,
|
|
WorkingDiagnosis: workingDiagnosis,
|
|
Notes: notes,
|
|
Channel: channel,
|
|
}}, ""
|
|
}
|
|
|
|
func requiredString(payload map[string]any, key string, maxLength int, requiredMessage string) (string, string) {
|
|
value, exists := payload[key]
|
|
if !exists || isLaravelEmpty(value) {
|
|
return "", requiredMessage
|
|
}
|
|
text, ok := value.(string)
|
|
if !ok {
|
|
return "", fmt.Sprintf("The %s must be a string.", validationAttribute(key))
|
|
}
|
|
if maxLength > 0 && utf8.RuneCountInString(text) > maxLength {
|
|
return "", fmt.Sprintf("The %s must not be greater than %d characters.", validationAttribute(key), maxLength)
|
|
}
|
|
return text, ""
|
|
}
|
|
|
|
func optionalString(payload map[string]any, key string, maxLength int) (string, bool, string) {
|
|
value, present := nullableValue(payload, key)
|
|
if !present {
|
|
return "", false, ""
|
|
}
|
|
text, ok := value.(string)
|
|
if !ok {
|
|
return "", false, fmt.Sprintf("The %s must be a string.", validationAttribute(key))
|
|
}
|
|
if maxLength > 0 && utf8.RuneCountInString(text) > maxLength {
|
|
return "", false, fmt.Sprintf("The %s must not be greater than %d characters.", validationAttribute(key), maxLength)
|
|
}
|
|
return text, true, ""
|
|
}
|
|
|
|
func requiredInteger(payload map[string]any, key string, requiredMessage string) (int64, string) {
|
|
value, exists := payload[key]
|
|
if !exists || isLaravelEmpty(value) {
|
|
return 0, requiredMessage
|
|
}
|
|
parsed, ok := parseInteger(value)
|
|
if !ok {
|
|
return 0, fmt.Sprintf("The %s must be an integer.", validationAttribute(key))
|
|
}
|
|
return parsed, ""
|
|
}
|
|
|
|
func nullableValue(payload map[string]any, key string) (any, bool) {
|
|
value, exists := payload[key]
|
|
if !exists || value == nil || value == "" {
|
|
return nil, false
|
|
}
|
|
return value, true
|
|
}
|
|
|
|
func parseInteger(value any) (int64, bool) {
|
|
switch typed := value.(type) {
|
|
case json.Number:
|
|
if integerValue.MatchString(typed.String()) {
|
|
parsed, err := strconv.ParseInt(typed.String(), 10, 64)
|
|
return parsed, err == nil
|
|
}
|
|
parsedFloat, err := strconv.ParseFloat(typed.String(), 64)
|
|
if err != nil || math.Trunc(parsedFloat) != parsedFloat || parsedFloat > math.MaxInt64 || parsedFloat < math.MinInt64 {
|
|
return 0, false
|
|
}
|
|
return int64(parsedFloat), true
|
|
case string:
|
|
typed = strings.TrimSpace(typed)
|
|
if !integerValue.MatchString(typed) || hasInvalidIntegerLeadingZero(typed) {
|
|
return 0, false
|
|
}
|
|
parsed, err := strconv.ParseInt(typed, 10, 64)
|
|
return parsed, err == nil
|
|
case bool:
|
|
if typed {
|
|
return 1, true
|
|
}
|
|
return 0, false
|
|
default:
|
|
return 0, false
|
|
}
|
|
}
|
|
|
|
func hasInvalidIntegerLeadingZero(value string) bool {
|
|
digits := strings.TrimPrefix(strings.TrimPrefix(value, "+"), "-")
|
|
return len(digits) > 1 && strings.HasPrefix(digits, "0")
|
|
}
|
|
|
|
func isLaravelEmpty(value any) bool {
|
|
if value == nil || value == "" {
|
|
return true
|
|
}
|
|
switch typed := value.(type) {
|
|
case []any:
|
|
return len(typed) == 0
|
|
case map[string]any:
|
|
return len(typed) == 0
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func validDateFormat(value string, location *time.Location) bool {
|
|
parsed, err := time.ParseInLocation("2006-01-02", value, location)
|
|
return err == nil && parsed.Format("2006-01-02") == value
|
|
}
|
|
|
|
func consistsOfOneRepeatedRune(value string) bool {
|
|
var first rune
|
|
count := 0
|
|
for _, current := range value {
|
|
if count == 0 {
|
|
first = current
|
|
} else if current != first {
|
|
return false
|
|
}
|
|
count++
|
|
}
|
|
return count > 0
|
|
}
|
|
|
|
func validationAttribute(key string) string {
|
|
return strings.ReplaceAll(key, "_", " ")
|
|
}
|
|
|
|
func writeLabResponse(
|
|
w http.ResponseWriter,
|
|
statusCode int,
|
|
success bool,
|
|
status string,
|
|
message string,
|
|
data *repository.LabRegistrationData,
|
|
) {
|
|
WriteJSON(w, statusCode, LabRegistrationResponse{
|
|
Success: success,
|
|
Status: status,
|
|
Message: message,
|
|
Data: data,
|
|
})
|
|
}
|